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,60 +49,89 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
return; return;
} }
// 1. select Azure function from the current file
const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, {
canPickMany: false,
title: constants.selectAzureFunction,
ignoreFocusOut: true
}));
if (!azureFunctionName) {
return;
}
// 2. select input or output binding
const selectedBinding = await azureFunctionsUtils.promptForBindingType();
if (!selectedBinding) {
return;
}
// 3. ask for object name for the binding
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding.type);
if (!objectName) {
return;
}
// 4. ask for connection string setting name
let projectUri: vscode.Uri | undefined;
try { try {
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri); // 1. select Azure function from the current file
} catch (e) { quickPickStep = 'getAzureFunctionProject';
// 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 const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, {
} canPickMany: false,
title: constants.selectAzureFunction,
ignoreFocusOut: true
}));
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri); if (!azureFunctionName) {
if (!connectionStringSettingName) {
return;
}
// 5. insert binding
try {
const result = await addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
if (!result.success) {
void vscode.window.showErrorMessage(result.errorMessage);
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding);
return; return;
} }
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getAzureFunctionProject)
.withAdditionalProperties(propertyBag).send();
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding) // 2. select input or output binding
.withAdditionalProperties({ bindingType: selectedBinding.label }) quickPickStep = 'getBindingType';
.send(); const selectedBinding = await azureFunctionsUtils.promptForBindingType();
if (!selectedBinding) {
return;
}
propertyBag.bindingType = selectedBinding.type;
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getBindingType)
.withAdditionalProperties(propertyBag).send();
// 3. ask for object name for the binding
quickPickStep = 'getObjectName';
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding.type);
if (!objectName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
.withAdditionalProperties(propertyBag).send();
// 4. ask for connection string setting name
let projectUri: vscode.Uri | undefined;
try {
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri);
} catch (e) {
// 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);
if (!connectionStringSettingName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.updateConnectionString)
.withAdditionalProperties(propertyBag).send();
// 5. insert binding
try {
quickPickStep = 'insertBinding';
const result = await addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
if (!result.success) {
void vscode.window.showErrorMessage(result.errorMessage);
TelemetryReporter.createErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
.withAdditionalProperties(propertyBag).send();
return;
}
exitReason = 'done';
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
.withAdditionalProperties(propertyBag).send();
} catch (e) {
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
TelemetryReporter.createErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding, undefined, utils.getErrorType(e))
.withAdditionalProperties(propertyBag).send();
return;
}
} catch (e) { } catch (e) {
propertyBag.quickPickStep = quickPickStep;
exitReason = 'error';
void vscode.window.showErrorMessage(utils.getErrorMessage(e)); void vscode.window.showErrorMessage(utils.getErrorMessage(e));
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding);
return; 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'
} }
/** /**