Adding start and end activity function (#18328)

* Adding standard activity tracking method

* renaming interfaces

* Fixing more helper functions

* Removing duplicate identifier

* adding update activity and code cleanup
This commit is contained in:
Aasim Khan 2024-10-31 13:25:21 -07:00 коммит произвёл GitHub
Родитель 2a3274e27d
Коммит 1360ff7ece
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 276 добавлений и 98 удалений

Просмотреть файл

@ -5,6 +5,12 @@
import * as vscode from "vscode";
import {
ActivityStatus,
ActivityObject,
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
import {
AuthenticationType,
AzureSubscriptionInfo,
@ -34,7 +40,11 @@ import {
fetchServersFromAzure,
promptForAzureSubscriptionFilter,
} from "./azureHelper";
import { getErrorMessage } from "../utils/utils";
import {
sendActionEvent,
sendErrorEvent,
startActivity,
} from "../telemetry/telemetry";
import { ApiStatus } from "../sharedInterfaces/webview";
import { AzureController } from "../azure/azureController";
@ -49,12 +59,8 @@ import { UserSurvey } from "../nps/userSurvey";
import VscodeWrapper from "../controllers/vscodeWrapper";
import { connectionCertValidationFailedErrorCode } from "./connectionConstants";
import { getConnectionDisplayName } from "../models/connectionInfo";
import { getErrorMessage } from "../utils/utils";
import { l10n } from "vscode";
import {
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
import { sendActionEvent, sendErrorEvent } from "../telemetry/telemetry";
export class ConnectionDialogWebviewController extends ReactWebviewPanelController<
ConnectionDialogWebviewState,
@ -1118,6 +1124,7 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
private async loadAzureSubscriptions(
state: ConnectionDialogWebviewState,
): Promise<Map<string, AzureSubscription[]> | undefined> {
let endActivity: ActivityObject;
try {
const auth = await confirmVscodeAzureSignin();
@ -1138,7 +1145,10 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
string[] | undefined
>(azureSubscriptionFilterConfigKey) !== undefined;
const startTime = Date.now();
endActivity = startActivity(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureSubscriptions,
);
this._azureSubscriptions = new Map(
(await auth.getSubscriptions(shouldUseFilter)).map((s) => [
@ -1166,16 +1176,13 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
state.azureSubscriptions = subs;
state.loadingAzureSubscriptionsStatus = ApiStatus.Loaded;
sendActionEvent(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureSubscriptions,
endActivity.end(
ActivityStatus.Succeeded,
undefined, // additionalProperties
{
subscriptionCount: subs.length,
msToLoadSubscriptions: Date.now() - startTime,
},
);
this.updateState();
return tenantSubMap;
@ -1183,14 +1190,7 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
state.formError = l10n.t("Error loading Azure subscriptions.");
state.loadingAzureSubscriptionsStatus = ApiStatus.Error;
console.error(state.formError + "\n" + getErrorMessage(error));
sendErrorEvent(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureSubscriptions,
error,
false, // includeErrorMessage
);
endActivity.endFailed(error, false);
return undefined;
}
}
@ -1198,6 +1198,10 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
private async loadAllAzureServers(
state: ConnectionDialogWebviewState,
): Promise<void> {
const endActivity = startActivity(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureServers,
);
try {
const tenantSubMap = await this.loadAzureSubscriptions(state);
@ -1213,9 +1217,6 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
state.loadingAzureServersStatus = ApiStatus.Loading;
state.azureServers = [];
this.updateState();
const startTime = Date.now();
const promiseArray: Promise<void>[] = [];
for (const t of tenantSubMap.keys()) {
for (const s of tenantSubMap.get(t)) {
@ -1228,14 +1229,11 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
}
}
await Promise.all(promiseArray);
sendActionEvent(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureServers,
endActivity.end(
ActivityStatus.Succeeded,
undefined, // additionalProperties
{
subscriptionCount: promiseArray.length,
msToLoadServers: Date.now() - startTime,
},
);
@ -1247,13 +1245,10 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
state.loadingAzureServersStatus = ApiStatus.Error;
console.error(state.formError + "\n" + getErrorMessage(error));
sendErrorEvent(
TelemetryViews.ConnectionDialog,
TelemetryActions.LoadAzureServers,
endActivity.endFailed(
error,
false, // includeErrorMessage
);
return;
}
}

Просмотреть файл

@ -6,6 +6,7 @@
import * as vscode from "vscode";
import {
ActivityStatus,
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
@ -13,7 +14,11 @@ import {
WebviewTelemetryActionEvent,
WebviewTelemetryErrorEvent,
} from "../sharedInterfaces/webview";
import { sendActionEvent, sendErrorEvent } from "../telemetry/telemetry";
import {
sendActionEvent,
sendErrorEvent,
startActivity,
} from "../telemetry/telemetry";
import { getNonce } from "../utils/utils";
@ -47,36 +52,68 @@ export abstract class ReactWebviewBaseController<State, Reducers>
>;
private _isFirstLoad: boolean = true;
private _loadStartTime: number = Date.now();
private _endLoadActivity = startActivity(
TelemetryViews.WebviewController,
TelemetryActions.Load,
);
private _onDisposed: vscode.EventEmitter<void> =
new vscode.EventEmitter<void>();
public readonly onDisposed: vscode.Event<void> = this._onDisposed.event;
protected _webviewMessageHandler = async (message) => {
if (message.type === "request") {
const endActivity = startActivity(
TelemetryViews.WebviewController,
TelemetryActions.WebviewRequest,
);
const handler = this._webviewRequestHandlers[message.method];
if (handler) {
const startTime = Date.now();
const result = await handler(message.params);
this.postMessage({ type: "response", id: message.id, result });
const endTime = Date.now();
sendActionEvent(
TelemetryViews.WebviewController,
TelemetryActions.WebviewRequest,
{
try {
const result = await handler(message.params);
this.postMessage({
type: "response",
id: message.id,
result,
});
endActivity.end(ActivityStatus.Succeeded, {
type: this._sourceFile,
method: message.method,
reducer:
message.method === "action"
? message.params.type
: undefined,
},
{
durationMs: endTime - startTime,
},
);
});
} catch (error) {
endActivity.endFailed(
error,
false,
"RequestHandlerFailed",
"RequestHandlerFailed",
{
type: this._sourceFile,
method: message.method,
reducer:
message.method === "action"
? message.params.type
: undefined,
},
);
throw error;
}
} else {
throw new Error(
const error = new Error(
`No handler registered for method ${message.method}`,
);
endActivity.endFailed(
error,
true,
"NoHandlerRegistered",
"NoHandlerRegistered",
{
type: this._sourceFile,
method: message.method,
},
);
throw error;
}
}
};
@ -189,16 +226,9 @@ export abstract class ReactWebviewBaseController<State, Reducers>
"\n" +
`Total time: ${timeToLoad} ms`,
);
sendActionEvent(
TelemetryViews.WebviewController,
TelemetryActions.Load,
{
type: this._sourceFile,
},
{
durationMs: timeToLoad,
},
);
this._endLoadActivity.end(ActivityStatus.Succeeded, {
type: this._sourceFile,
});
this._isFirstLoad = false;
}
};

Просмотреть файл

@ -58,3 +58,68 @@ export enum TelemetryActions {
LoadAzureSubscriptions = "LoadAzureSubscriptions",
OpenExecutionPlan = "OpenExecutionPlan",
}
/**
* The status of an activity
*/
export enum ActivityStatus {
Succeeded = "Succeeded",
Pending = "Pending",
Failed = "Failed",
Canceled = "Canceled",
}
/**
* Finish an activity. This should be called when the activity is complete to send the final telemetry event
*/
export type FinishActivity = (
activityStatus: Exclude<ActivityStatus, ActivityStatus.Failed>,
additionalProperties?: Record<string, string>,
additionalMeasurements?: Record<string, number>,
) => void;
/**
* Finish an activity with a failure. This should be called when the activity fails to send the final telemetry event
*/
export type FinishActivityFailed = (
error?: Error,
includeErrorMessage?: boolean,
errorCode?: string,
errorType?: string,
additionalProperties?: Record<string, string>,
additionalMeasurements?: Record<string, number>,
) => void;
/**
* Update an activity. This should be called when the activity is still in progress to send intermediate telemetry events
*/
export type UpdateActivity = (
additionalProperties?: Record<string, string>,
additionalMeasurements?: Record<string, number>,
) => void;
/**
* An object that contains the functions to update and finish an activity. This is returned when an activity is started
*/
export type ActivityObject = {
/**
* Update the activity with additional properties and measurements
*/
update: UpdateActivity;
/**
* Finish the activity
*/
end: FinishActivity;
/**
* Finish the activity with a failure
*/
endFailed: FinishActivityFailed;
/**
* The correlation id for the activity
*/
correlationId: string;
/**
* The start time of the activity generated by performance.now()
*/
startTime: number;
};

Просмотреть файл

@ -11,8 +11,9 @@ import * as designer from "../sharedInterfaces/tableDesigner";
import UntitledSqlDocumentService from "../controllers/untitledSqlDocumentService";
import { getDesignerView } from "./tableDesignerTabDefinition";
import { TreeNodeInfo } from "../objectExplorer/treeNodeInfo";
import { sendActionEvent } from "../telemetry/telemetry";
import { sendActionEvent, startActivity } from "../telemetry/telemetry";
import {
ActivityStatus,
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
@ -70,7 +71,6 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
}
private async initialize() {
const intializeStartTime = Date.now();
if (!this._targetNode) {
await vscode.window.showErrorMessage(
"Unable to find object explorer node",
@ -109,6 +109,16 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
return;
}
const endActivity = startActivity(
TelemetryViews.TableDesigner,
TelemetryActions.Initialize,
this._correlationId,
{
correlationId: this._correlationId,
isEdit: this._isEdit.toString(),
},
);
try {
let tableInfo: designer.TableInfo;
if (this._isEdit) {
@ -135,28 +145,17 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
};
}
this.panel.title = tableInfo.title;
const intializeData =
const initializeResult =
await this._tableDesignerService.initializeTableDesigner(
tableInfo,
);
const intializeEndTime = Date.now();
sendActionEvent(
TelemetryViews.TableDesigner,
TelemetryActions.Initialize,
{
correlationId: this._correlationId,
isEdit: this._isEdit.toString(),
},
{
durationMs: intializeEndTime - intializeStartTime,
},
);
intializeData.tableInfo.database = databaseName ?? "master";
endActivity.end(ActivityStatus.Succeeded);
initializeResult.tableInfo.database = databaseName ?? "master";
this.state = {
tableInfo: tableInfo,
view: getDesignerView(intializeData.view),
model: intializeData.viewModel,
issues: intializeData.issues,
view: getDesignerView(initializeResult.view),
model: initializeResult.viewModel,
issues: initializeResult.issues,
isValid: true,
tabStates: {
mainPaneTab: designer.DesignerMainPaneTabs.Columns,
@ -168,6 +167,7 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
},
};
} catch (e) {
endActivity.endFailed(e, false);
this.state.apiState.initializeState = designer.LoadState.Error;
this.state = this.state;
}
@ -235,6 +235,14 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
});
this.registerReducer("publishChanges", async (state, payload) => {
const endActivity = startActivity(
TelemetryViews.TableDesigner,
TelemetryActions.Publish,
this._correlationId,
{
correlationId: this._correlationId,
},
);
this.state = {
...this.state,
apiState: {
@ -247,14 +255,7 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
await this._tableDesignerService.publishChanges(
payload.table,
);
sendActionEvent(
TelemetryViews.TableDesigner,
TelemetryActions.Publish,
{
correlationId: this._correlationId,
},
);
endActivity.end(ActivityStatus.Succeeded);
state = {
...state,
tableInfo: publishResponse.newTableInfo,
@ -278,14 +279,7 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
},
publishingError: e.toString(),
};
sendActionEvent(
TelemetryViews.TableDesigner,
TelemetryActions.Publish,
{
correlationId: this._correlationId,
error: "true",
},
);
endActivity.endFailed(e, false);
}
let targetNode = this._targetNode;

Просмотреть файл

@ -3,17 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import * as vscodeMssql from "vscode-mssql";
import {
ActivityStatus,
ActivityObject,
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
import AdsTelemetryReporter, {
TelemetryEventMeasures,
TelemetryEventProperties,
} from "@microsoft/ads-extension-telemetry";
import * as vscodeMssql from "vscode-mssql";
import { IConnectionProfile } from "../models/interfaces";
import * as vscode from "vscode";
import {
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
import { v4 as uuidv4 } from "uuid";
const packageJson = vscode.extensions.getExtension(
vscodeMssql.extension.name,
@ -110,3 +115,92 @@ export function sendErrorEvent(
}
errorEvent.send();
}
export function startActivity(
telemetryView: TelemetryViews,
telemetryAction: TelemetryActions,
correlationId?: string,
additionalProps: TelemetryEventProperties = {},
additionalMeasurements: TelemetryEventMeasures = {},
): ActivityObject {
const startTime = performance.now();
if (!correlationId) {
correlationId = uuidv4();
}
sendActionEvent(telemetryView, telemetryAction, additionalProps, {
...additionalMeasurements,
startTime: Math.round(startTime),
});
function update(
additionalProps: TelemetryEventProperties,
additionalMeasurements: TelemetryEventMeasures,
): void {
sendActionEvent(
telemetryView,
telemetryAction,
{
...additionalProps,
activityStatus: ActivityStatus.Pending,
},
{
...additionalMeasurements,
timeElapsedMs: Math.round(performance.now() - startTime),
},
);
}
function end(
activityStatus: ActivityStatus,
additionalProps: TelemetryEventProperties,
additionalMeasurements: TelemetryEventMeasures,
) {
sendActionEvent(
telemetryView,
telemetryAction,
{
...additionalProps,
activityStatus: activityStatus,
},
{
...additionalMeasurements,
durationMs: Math.round(performance.now() - startTime),
},
);
}
function endFailed(
error?: Error,
includeErrorMessage?: boolean,
errorCode?: string,
errorType?: string,
additionalProps?: TelemetryEventProperties,
additionalMeasurements?: TelemetryEventMeasures,
) {
sendErrorEvent(
telemetryView,
telemetryAction,
error,
includeErrorMessage,
errorCode,
errorType,
{
...additionalProps,
activityStatus: ActivityStatus.Failed,
},
{
...additionalMeasurements,
durationMs: Math.round(performance.now() - startTime),
},
);
}
return {
startTime,
correlationId,
update,
end,
endFailed,
};
}