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 enum TelemetryViews {
SqlBindingsQuickPick = 'SqlBindingsQuickPick'
SqlBindingsQuickPick = 'SqlBindingsQuickPick',
CreateAzureFunctionWithSqlBinding = 'CreateAzureFunctionWithSqlBinding'
}
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',
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;
}
export class TimeoutError extends Error { }
/**
* 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> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(errorMessage));
reject(new TimeoutError(errorMessage));
}, ms);
});
}
@@ -173,3 +175,11 @@ export function getPackageInfo(): IPackageInfo {
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';
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);
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
@@ -45,60 +49,89 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
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 {
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
}
// 1. select Azure function from the current file
quickPickStep = 'getAzureFunctionProject';
const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, {
canPickMany: false,
title: constants.selectAzureFunction,
ignoreFocusOut: true
}));
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
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);
if (!azureFunctionName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getAzureFunctionProject)
.withAdditionalProperties(propertyBag).send();
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
.withAdditionalProperties({ bindingType: selectedBinding.label })
.send();
// 2. select input or output binding
quickPickStep = 'getBindingType';
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) {
propertyBag.quickPickStep = quickPickStep;
exitReason = 'error';
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 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 {
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 constants from '../common/constants';
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
import { IConnectionInfo } from 'vscode-mssql';
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();
if (!azureFunctionApi) {
propertyBag.exitReason = exitReason;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
return;
}
let projectFile = await azureFunctionsUtils.getAzureFunctionProject();
let newHostProjectFile!: azureFunctionsUtils.IFileFunctionObject;
@@ -27,9 +40,18 @@ export async function createAzureFunction(connectionString: string, schema: stri
let projectCreate = await vscode.window.showErrorMessage(constants.azureFunctionsProjectMustBeOpened,
constants.createProject, constants.learnMore);
if (projectCreate === constants.learnMore) {
quickPickStep = 'learnMore';
void vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(constants.sqlBindingsDoc));
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.learnMore)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
return;
} 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
try {
// 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) {
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;
} finally {
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
newHostProjectFile.watcherDisposable.dispose();
}
}
@@ -59,6 +86,7 @@ export async function createAzureFunction(connectionString: string, schema: stri
try {
// get function name from user
quickPickStep = 'getAzureFunctionName';
let uniqueFunctionName = await utils.getUniqueFileName(path.dirname(projectFile), table);
functionName = await vscode.window.showInputBox({
title: constants.functionNameTitle,
@@ -69,13 +97,21 @@ export async function createAzureFunction(connectionString: string, schema: stri
if (!functionName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.getAzureFunctionProject)
.withAdditionalProperties(propertyBag)
.withConnectionInfo(connectionInfo).send();
// select input or output binding
quickPickStep = 'getBindingType';
const selectedBinding = await azureFunctionsUtils.promptForBindingType();
if (!selectedBinding) {
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
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
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
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 {
propertyBag.quickPickStep = quickPickStep;
propertyBag.exitReason = exitReason;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
.withConnectionInfo(connectionInfo)
.withAdditionalProperties(propertyBag).send();
newFunctionFileObject.watcherDisposable.dispose();
}
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
*/
export const enum BindingType {
input,
output
input = 'input',
output = 'output'
}
/**