Add bindings telemetry (#18851)

* add bindings telemetry

* add exitReasons
This commit is contained in:
Vasu Bhog
2022-03-31 16:29:15 -07:00
committed by GitHub
parent 80db3a492f
commit 0a43ed84e3
6 changed files with 167 additions and 55 deletions

View File

@@ -10,10 +10,24 @@ const packageInfo = getPackageInfo()!;
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey); export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews { export enum TelemetryViews {
SqlBindingsQuickPick = 'SqlBindingsQuickPick' SqlBindingsQuickPick = 'SqlBindingsQuickPick',
CreateAzureFunctionWithSqlBinding = 'CreateAzureFunctionWithSqlBinding'
} }
export enum TelemetryActions { export enum TelemetryActions {
// Create Azure Function with Sql Binding from Table
startCreateAzureFunctionWithSqlBinding = 'startCreateAzureFunctionWithSqlBinding',
helpCreateAzureFunctionProject = 'helpCreateAzureFunctionProject',
learnMore = 'learnMore',
finishCreateAzureFunctionWithSqlBinding = 'finishCreateAzureFunctionWithSqlBinding',
exitCreateAzureFunctionQuickpick = 'exitCreateAzureFunctionQuickpick',
// Add SQL Binding to Azure Function
startAddSqlBinding = 'startAddSqlBinding', startAddSqlBinding = 'startAddSqlBinding',
finishAddSqlBinding = 'finishAddSqlBinding' getAzureFunctionProject = 'getAzureFunctionProject',
getBindingType = 'getBindingType',
getObjectName = 'getObjectName',
updateConnectionString = 'updateConnectionString',
finishAddSqlBinding = 'finishAddSqlBinding',
exitSqlBindingsQuickpick = 'exitSqlBindingsQuickpick',
} }

View File

@@ -23,6 +23,8 @@ export interface IPackageInfo {
aiKey: string; aiKey: string;
} }
export class TimeoutError extends Error { }
/** /**
* Consolidates on the error message string * Consolidates on the error message string
*/ */
@@ -128,7 +130,7 @@ export function generateQuotedFullName(schema: string, objectName: string): stri
export function timeoutPromise(errorMessage: string, ms: number = 10000): Promise<string> { export function timeoutPromise(errorMessage: string, ms: number = 10000): Promise<string> {
return new Promise((_, reject) => { return new Promise((_, reject) => {
setTimeout(() => { setTimeout(() => {
reject(new Error(errorMessage)); reject(new TimeoutError(errorMessage));
}, ms); }, ms);
}); });
} }
@@ -173,3 +175,11 @@ export function getPackageInfo(): IPackageInfo {
aiKey: packageJson.aiKey aiKey: packageJson.aiKey
}; };
} }
export function getErrorType(error: any): string | undefined {
if (error instanceof TimeoutError) {
return 'TimeoutError';
} else {
return 'UnknownError';
}
}

View File

@@ -11,6 +11,10 @@ import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/t
import { addSqlBinding, getAzureFunctions } from '../services/azureFunctionsService'; import { addSqlBinding, getAzureFunctions } from '../services/azureFunctionsService';
export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined): Promise<void> { export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined): Promise<void> {
let quickPickStep: string = '';
let exitReason: string = 'cancelled';
let propertyBag: { [key: string]: string } = {};
TelemetryReporter.sendActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.startAddSqlBinding); TelemetryReporter.sendActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.startAddSqlBinding);
if (!uri) { if (!uri) {
// this command only shows in the command palette when the active editor is a .cs file, so we can safely assume that's the scenario // this command only shows in the command palette when the active editor is a .cs file, so we can safely assume that's the scenario
@@ -45,7 +49,9 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
return; return;
} }
try {
// 1. select Azure function from the current file // 1. select Azure function from the current file
quickPickStep = 'getAzureFunctionProject';
const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, { const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, {
canPickMany: false, canPickMany: false,
title: constants.selectAzureFunction, title: constants.selectAzureFunction,
@@ -55,20 +61,29 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
if (!azureFunctionName) { if (!azureFunctionName) {
return; return;
} }
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getAzureFunctionProject)
.withAdditionalProperties(propertyBag).send();
// 2. select input or output binding // 2. select input or output binding
quickPickStep = 'getBindingType';
const selectedBinding = await azureFunctionsUtils.promptForBindingType(); const selectedBinding = await azureFunctionsUtils.promptForBindingType();
if (!selectedBinding) { if (!selectedBinding) {
return; return;
} }
propertyBag.bindingType = selectedBinding.type;
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getBindingType)
.withAdditionalProperties(propertyBag).send();
// 3. ask for object name for the binding // 3. ask for object name for the binding
quickPickStep = 'getObjectName';
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding.type); const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding.type);
if (!objectName) { if (!objectName) {
return; return;
} }
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
.withAdditionalProperties(propertyBag).send();
// 4. ask for connection string setting name // 4. ask for connection string setting name
let projectUri: vscode.Uri | undefined; let projectUri: vscode.Uri | undefined;
@@ -78,27 +93,45 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
// continue even if there's no AF project found. The binding should still be able to be added as long as there was an azure function found in the file earlier // continue even if there's no AF project found. The binding should still be able to be added as long as there was an azure function found in the file earlier
} }
quickPickStep = 'updateConnectionString';
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri); let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
if (!connectionStringSettingName) { if (!connectionStringSettingName) {
return; return;
} }
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.updateConnectionString)
.withAdditionalProperties(propertyBag).send();
// 5. insert binding // 5. insert binding
try { try {
quickPickStep = 'insertBinding';
const result = await addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName); const result = await addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
if (!result.success) { if (!result.success) {
void vscode.window.showErrorMessage(result.errorMessage); void vscode.window.showErrorMessage(result.errorMessage);
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding); TelemetryReporter.createErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
.withAdditionalProperties(propertyBag).send();
return; return;
} }
exitReason = 'done';
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding) TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
.withAdditionalProperties({ bindingType: selectedBinding.label }) .withAdditionalProperties(propertyBag).send();
.send();
} catch (e) { } catch (e) {
void vscode.window.showErrorMessage(utils.getErrorMessage(e)); void vscode.window.showErrorMessage(utils.getErrorMessage(e));
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding); TelemetryReporter.createErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding, undefined, utils.getErrorType(e))
.withAdditionalProperties(propertyBag).send();
return; return;
} }
} catch (e) {
propertyBag.quickPickStep = quickPickStep;
exitReason = 'error';
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
TelemetryReporter.createErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.exitSqlBindingsQuickpick, undefined, utils.getErrorType(e))
.withAdditionalProperties(propertyBag).send();
} finally {
propertyBag.quickPickStep = quickPickStep;
propertyBag.exitReason = exitReason;
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.exitSqlBindingsQuickpick)
.withAdditionalProperties(propertyBag).send();
}
} }

View File

@@ -31,7 +31,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
} }
const connectionDetails = vscodeMssqlApi.createConnectionDetails(connectionInfo); const connectionDetails = vscodeMssqlApi.createConnectionDetails(connectionInfo);
const connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, false, false); const connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, false, false);
await createAzureFunction(connectionString, node.metadata.schema, node.metadata.name); await createAzureFunction(connectionString, node.metadata.schema, node.metadata.name, connectionInfo);
})); }));
return { return {
addSqlBinding: async (bindingType: BindingType, filePath: string, functionName: string, objectName: string, connectionStringSetting: string) => { addSqlBinding: async (bindingType: BindingType, filePath: string, functionName: string, objectName: string, connectionStringSetting: string) => {

View File

@@ -9,15 +9,28 @@ import * as utils from '../common/utils';
import * as azureFunctionsUtils from '../common/azureFunctionsUtils'; import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts'; import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings'; import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
import { IConnectionInfo } from 'vscode-mssql';
export const hostFileName: string = 'host.json'; export const hostFileName: string = 'host.json';
export async function createAzureFunction(connectionString: string, schema: string, table: string, connectionInfo: IConnectionInfo): Promise<void> {
let propertyBag: { [key: string]: string } = {};
let quickPickStep: string = '';
let exitReason: string = 'cancelled';
export async function createAzureFunction(connectionString: string, schema: string, table: string): Promise<void> { TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
.withConnectionInfo(connectionInfo).send();
quickPickStep = 'getAzureFunctionsExtensionApi';
const azureFunctionApi = await azureFunctionsUtils.getAzureFunctionsExtensionApi(); const azureFunctionApi = await azureFunctionsUtils.getAzureFunctionsExtensionApi();
if (!azureFunctionApi) { if (!azureFunctionApi) {
propertyBag.exitReason = exitReason;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
return; return;
} }
let projectFile = await azureFunctionsUtils.getAzureFunctionProject(); let projectFile = await azureFunctionsUtils.getAzureFunctionProject();
let newHostProjectFile!: azureFunctionsUtils.IFileFunctionObject; let newHostProjectFile!: azureFunctionsUtils.IFileFunctionObject;
@@ -27,9 +40,18 @@ export async function createAzureFunction(connectionString: string, schema: stri
let projectCreate = await vscode.window.showErrorMessage(constants.azureFunctionsProjectMustBeOpened, let projectCreate = await vscode.window.showErrorMessage(constants.azureFunctionsProjectMustBeOpened,
constants.createProject, constants.learnMore); constants.createProject, constants.learnMore);
if (projectCreate === constants.learnMore) { if (projectCreate === constants.learnMore) {
quickPickStep = 'learnMore';
void vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(constants.sqlBindingsDoc)); void vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(constants.sqlBindingsDoc));
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.learnMore)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
return; return;
} else if (projectCreate === constants.createProject) { } else if (projectCreate === constants.createProject) {
quickPickStep = 'helpCreateAzureFunctionProject';
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.helpCreateAzureFunctionProject)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
// start the create azure function project flow // start the create azure function project flow
try { try {
// because of an AF extension API issue, we have to get the newly created file by adding a watcher // because of an AF extension API issue, we have to get the newly created file by adding a watcher
@@ -44,8 +66,13 @@ export async function createAzureFunction(connectionString: string, schema: stri
} }
} catch (error) { } catch (error) {
void vscode.window.showErrorMessage(utils.formatString(constants.errorNewAzureFunction, error.message ?? error)); void vscode.window.showErrorMessage(utils.formatString(constants.errorNewAzureFunction, error.message ?? error));
let errorType = utils.getErrorType(error);
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.helpCreateAzureFunctionProject, undefined, errorType).send();
return; return;
} finally { } finally {
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
newHostProjectFile.watcherDisposable.dispose(); newHostProjectFile.watcherDisposable.dispose();
} }
} }
@@ -59,6 +86,7 @@ export async function createAzureFunction(connectionString: string, schema: stri
try { try {
// get function name from user // get function name from user
quickPickStep = 'getAzureFunctionName';
let uniqueFunctionName = await utils.getUniqueFileName(path.dirname(projectFile), table); let uniqueFunctionName = await utils.getUniqueFileName(path.dirname(projectFile), table);
functionName = await vscode.window.showInputBox({ functionName = await vscode.window.showInputBox({
title: constants.functionNameTitle, title: constants.functionNameTitle,
@@ -69,13 +97,21 @@ export async function createAzureFunction(connectionString: string, schema: stri
if (!functionName) { if (!functionName) {
return; return;
} }
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.getAzureFunctionProject)
.withAdditionalProperties(propertyBag)
.withConnectionInfo(connectionInfo).send();
// select input or output binding // select input or output binding
quickPickStep = 'getBindingType';
const selectedBinding = await azureFunctionsUtils.promptForBindingType(); const selectedBinding = await azureFunctionsUtils.promptForBindingType();
if (!selectedBinding) { if (!selectedBinding) {
return; return;
} }
propertyBag.bindingType = selectedBinding.type;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
.withAdditionalProperties(propertyBag)
.withConnectionInfo(connectionInfo).send();
// set the templateId based on the selected binding type // set the templateId based on the selected binding type
let templateId: string = selectedBinding.type === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID; let templateId: string = selectedBinding.type === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
@@ -103,10 +139,29 @@ export async function createAzureFunction(connectionString: string, schema: stri
// check for the new function file to be created and dispose of the file system watcher // check for the new function file to be created and dispose of the file system watcher
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError); const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]); await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]);
propertyBag.quickPickStep = quickPickStep;
exitReason = 'finishCreate';
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.finishCreateAzureFunctionWithSqlBinding)
.withAdditionalProperties(propertyBag)
.withConnectionInfo(connectionInfo).send();
} catch (e) {
propertyBag.quickPickStep = quickPickStep;
exitReason = 'error';
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick, undefined, utils.getErrorType(e))
.withAdditionalProperties(propertyBag).send();
} finally { } finally {
propertyBag.quickPickStep = quickPickStep;
propertyBag.exitReason = exitReason;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
newFunctionFileObject.watcherDisposable.dispose(); newFunctionFileObject.watcherDisposable.dispose();
} }
await azureFunctionsUtils.addConnectionStringToConfig(connectionString, projectFile); await azureFunctionsUtils.addConnectionStringToConfig(connectionString, projectFile);
} else {
TelemetryReporter.sendErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.finishCreateAzureFunctionWithSqlBinding);
} }
} }

View File

@@ -86,8 +86,8 @@ declare module 'sql-bindings' {
* Azure Functions binding type * Azure Functions binding type
*/ */
export const enum BindingType { export const enum BindingType {
input, input = 'input',
output output = 'output'
} }
/** /**