add telemetry to undo/redo at cell level (#17804)

* add telemetry to undo/redo at cell level

*add sendTelemetryActionEvent that sends telemetry from notebook model
This commit is contained in:
Barbara Valdez
2021-12-03 16:21:52 -08:00
committed by GitHub
parent cde4e05320
commit 8a205965d2
8 changed files with 57 additions and 30 deletions

View File

@@ -96,7 +96,9 @@ export const enum NbTelemetryAction {
RunAll = 'RunNotebook',
AddCell = 'AddCell',
KernelChanged = 'KernelChanged',
NewNotebookFromConnections = 'NewNotebookWithConnectionProfile'
NewNotebookFromConnections = 'NewNotebookWithConnectionProfile',
UndoCell = 'UndoCell',
RedoCell = 'RedoCell'
}
export const enum TelemetryPropertyName {

View File

@@ -61,7 +61,6 @@ export class AddCellAction extends Action {
constructor(
id: string, label: string, cssClass: string,
@INotebookService private _notebookService: INotebookService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) {
super(id, label, cssClass);
}
@@ -76,16 +75,15 @@ export class AddCellAction extends Action {
}
if (context?.model) {
context.model.addCell(this.cellType, index);
context.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.AddCell, { cell_type: this.cellType });
}
} else {
//Add Cell after current selected cell.
const editor = this._notebookService.findNotebookEditor(context);
const index = editor.cells?.findIndex(cell => cell.active) ?? 0;
editor.addCell(this.cellType, index);
editor.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.AddCell, { cell_type: this.cellType });
}
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.AddCell)
.withAdditionalProperties({ cell_type: this.cellType })
.send();
}
}
@@ -369,19 +367,13 @@ export class RunAllCellsAction extends Action {
id: string, label: string, cssClass: string,
@INotificationService private notificationService: INotificationService,
@INotebookService private _notebookService: INotebookService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) {
super(id, label, cssClass);
}
public override async run(context: URI): Promise<void> {
try {
const editor = this._notebookService.findNotebookEditor(context);
const azdata_notebook_guid: string = editor.model.getMetaValue('azdata_notebook_guid');
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.RunAll)
.withAdditionalProperties({ azdata_notebook_guid })
.send();
editor.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.RunAll);
await editor.runAllCells();
} catch (e) {
this.notificationService.error(getErrorMessage(e));

View File

@@ -29,6 +29,8 @@ import { MockQuickInputService } from 'sql/workbench/contrib/notebook/test/commo
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { Separator } from 'vs/base/common/actions';
import { INotebookView, INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
class TestClientSession extends ClientSessionStub {
private _errorState: boolean = false;
@@ -106,6 +108,9 @@ class TestNotebookModel extends NotebookModelStub {
public override getStandardKernelFromName(name: string): IStandardKernelWithProvider {
return this._standardKernelsMap.get(name);
}
public override sendNotebookTelemetryActionEvent(action: TelemetryKeys.TelemetryAction | TelemetryKeys.NbTelemetryAction, additionalProperties?: ITelemetryEventProperties): void {
}
}
suite('Notebook Actions', function (): void {
@@ -113,9 +118,11 @@ suite('Notebook Actions', function (): void {
let mockNotebookEditor: TypeMoq.Mock<INotebookEditor>;
let mockNotebookService: TypeMoq.Mock<INotebookService>;
const testUri = URI.parse('untitled');
let testNotebookModel = new TestNotebookModel();
suiteSetup(function (): void {
mockNotebookEditor = TypeMoq.Mock.ofType<INotebookEditor>(NotebookEditorStub);
mockNotebookEditor.setup(x => x.model).returns(() => testNotebookModel);
mockNotebookService = TypeMoq.Mock.ofType<INotebookService>(NotebookServiceStub);
mockNotebookService.setup(x => x.findNotebookEditor(TypeMoq.It.isAny())).returns(uri => mockNotebookEditor.object);
});
@@ -129,7 +136,7 @@ suite('Notebook Actions', function (): void {
let actualCellType: CellType;
let action = new AddCellAction('TestId', 'TestLabel', 'TestClass', mockNotebookService.object, new NullAdsTelemetryService());
let action = new AddCellAction('TestId', 'TestLabel', 'TestClass', mockNotebookService.object);
action.cellType = testCellType;
// Normal use case
@@ -196,7 +203,7 @@ suite('Notebook Actions', function (): void {
let mockNotification = TypeMoq.Mock.ofType<INotificationService>(TestNotificationService);
mockNotification.setup(n => n.notify(TypeMoq.It.isAny()));
let action = new RunAllCellsAction('TestId', 'TestLabel', 'TestClass', mockNotification.object, mockNotebookService.object, new NullAdsTelemetryService());
let action = new RunAllCellsAction('TestId', 'TestLabel', 'TestClass', mockNotification.object, mockNotebookService.object);
// Normal use case
mockNotebookEditor.setup(c => c.runAllCells()).returns(() => Promise.resolve(true));

View File

@@ -21,6 +21,8 @@ import { IEditorPane } from 'vs/workbench/common/editor';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
import { INotebookView, INotebookViewCell, INotebookViewMetadata, INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
export class NotebookModelStub implements INotebookModel {
constructor(private _languageInfo?: nb.ILanguageInfo, private _cells?: ICellModel[], private _testContents?: nb.INotebookContents) {
@@ -158,6 +160,8 @@ export class NotebookModelStub implements INotebookModel {
requestConnection(): Promise<boolean> {
throw new Error('Method not implemented.');
}
sendNotebookTelemetryActionEvent(action: TelemetryKeys.TelemetryAction | TelemetryKeys.NbTelemetryAction, additionalProperties?: ITelemetryEventProperties): void {
}
}
export class NotebookFindModelStub implements INotebookFindModel {

View File

@@ -29,7 +29,6 @@ import { tryMatchCellMagic, extractCellMagicCommandPlusArgs } from 'sql/workbenc
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IInsightOptions } from 'sql/workbench/common/editor/query/chartState';
import { IPosition } from 'vs/editor/common/core/position';
@@ -94,7 +93,6 @@ export class CellModel extends Disposable implements ICellModel {
@optional(INotebookService) private _notebookService?: INotebookService,
@optional(ICommandService) private _commandService?: ICommandService,
@optional(IConfigurationService) private _configurationService?: IConfigurationService,
@optional(IAdsTelemetryService) private _telemetryService?: IAdsTelemetryService,
) {
super();
this.id = `${modelId++}`;
@@ -568,10 +566,7 @@ export class CellModel extends Disposable implements ICellModel {
this._outputCounter = 0;
// Hide IntelliSense suggestions list when running cell to match SSMS behavior
this._commandService.executeCommand('hideSuggestWidget');
const azdata_notebook_guid: string = this.notebookModel.getMetaValue('azdata_notebook_guid');
this._telemetryService?.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.RunCell)
.withAdditionalProperties({ cell_language: kernel.name, azdata_cell_guid: this._cellGuid, azdata_notebook_guid })
.send();
this.notebookModel.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.RunCell, { cell_language: kernel.name, azdata_cell_guid: this._cellGuid });
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
// This matches the same behavior as JupyterLab
if (this.future && this.future.inProgress) {

View File

@@ -6,12 +6,14 @@
import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import { ICellModel, MoveDirection } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { localize } from 'vs/nls';
export class MoveCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = localize('moveCellEdit', "Move Cell");
resource = this.model.notebookUri;
private readonly cellOperation = { cell_operation: 'move_cell' };
constructor(private model: NotebookModel, private cell: ICellModel, private moveDirection: MoveDirection) {
}
@@ -19,10 +21,12 @@ export class MoveCellEdit implements IResourceUndoRedoElement {
undo(): void {
const direction = this.moveDirection === MoveDirection.Down ? MoveDirection.Up : MoveDirection.Down;
this.model.moveCell(this.cell, direction, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.UndoCell, this.cellOperation);
}
redo(): void {
this.model.moveCell(this.cell, this.moveDirection, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.RedoCell, this.cellOperation);
}
}
@@ -30,12 +34,14 @@ export class SplitCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = localize('splitCellEdit', "Split Cell");
resource = this.model.notebookUri;
private readonly cellOperation = { cell_operation: 'split_cell' };
constructor(private model: NotebookModel, private firstCell: ICellModel, private secondCell: ICellModel, private newLinesRemoved: string[]) {
}
undo(): void {
this.model.mergeCells(this.firstCell, this.secondCell, this.newLinesRemoved);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.UndoCell, this.cellOperation);
}
redo(): void {
@@ -47,16 +53,19 @@ export class DeleteCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = localize('deleteCellEdit', "Delete Cell");
resource = this.model.notebookUri;
private readonly cellOperation = { cell_operation: 'delete_cell' };
constructor(private model: NotebookModel, private cell: ICellModel, private index: number) {
}
undo(): void {
this.model.insertCell(this.cell, this.index, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.UndoCell, this.cellOperation);
}
redo(): void {
this.model.deleteCell(this.cell, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.RedoCell, this.cellOperation);
}
}
@@ -64,15 +73,18 @@ export class AddCellEdit implements IResourceUndoRedoElement {
type: UndoRedoElementType.Resource = UndoRedoElementType.Resource;
label: string = localize('addCellEdit', "Add Cell");
resource = this.model.notebookUri;
private readonly cellOperation = { cell_operation: 'add_cell' };
constructor(private model: NotebookModel, private cell: ICellModel, private index: number) {
}
undo(): void {
this.model.deleteCell(this.cell, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.UndoCell, this.cellOperation);
}
redo(): void {
this.model.insertCell(this.cell, this.index, false);
this.model.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.RedoCell, this.cellOperation);
}
}

View File

@@ -24,6 +24,9 @@ import type { FutureInternal } from 'sql/workbench/services/notebook/browser/int
import { ICellValue, ResultSetSummary } from 'sql/workbench/services/query/common/query';
import { QueryResultId } from 'sql/workbench/services/notebook/browser/models/cell';
import { IPosition } from 'vs/editor/common/core/position';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
export enum ViewMode {
Notebook,
@@ -439,6 +442,12 @@ export interface INotebookModel {
requestConnection(): Promise<boolean>;
/**
* Create and send a Notebook Telemetry Event
* @param action Telemetry action
* @param additionalProperties Additional properties to send.
*/
sendNotebookTelemetryActionEvent(action: TelemetryKeys.TelemetryAction | TelemetryKeys.NbTelemetryAction, additionalProperties?: ITelemetryEventProperties): void;
}
export interface NotebookContentChange {

View File

@@ -25,7 +25,7 @@ import { uriPrefixes } from 'sql/platform/connection/common/utils';
import { ILogService } from 'vs/platform/log/common/log';
import { getErrorMessage } from 'vs/base/common/errors';
import { notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
import { Deferred } from 'sql/base/common/promise';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
@@ -37,6 +37,8 @@ import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryText
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { AddCellEdit, DeleteCellEdit, MoveCellEdit, SplitCellEdit } from 'sql/workbench/services/notebook/browser/models/cellEdit';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { deepClone } from 'vs/base/common/objects';
/*
* Used to control whether a message in a dialog/wizard is displayed as an error,
* warning, or informational message. Default is error.
@@ -476,6 +478,14 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
}
public sendNotebookTelemetryActionEvent(action: TelemetryKeys.TelemetryAction | TelemetryKeys.NbTelemetryAction, additionalProperties: ITelemetryEventProperties = {}): void {
let properties: ITelemetryEventProperties = deepClone(additionalProperties);
properties['azdata_notebook_guid'] = this.getMetaValue('azdata_notebook_guid');
this.adstelemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, action)
.withAdditionalProperties(properties)
.send();
}
private loadContentMetadata(metadata: INotebookMetadataInternal): void {
this._savedKernelInfo = metadata.kernelspec;
this._defaultLanguageInfo = metadata.language_info;
@@ -491,9 +501,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (metadata.azdata_notebook_guid && metadata.azdata_notebook_guid.length === 36) {
//Verify if it is actual GUID and then send it to the telemetry
if (isUUID(metadata.azdata_notebook_guid)) {
this.adstelemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.TelemetryAction.Open)
.withAdditionalProperties({ azdata_notebook_guid: metadata.azdata_notebook_guid })
.send();
this.sendNotebookTelemetryActionEvent(TelemetryKeys.TelemetryAction.Open);
}
}
Object.keys(metadata).forEach(key => {
@@ -1162,12 +1170,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
} else if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info);
}
this.adstelemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.KernelChanged)
.withAdditionalProperties({
name: kernel.name,
alias: kernelAlias || ''
})
.send();
this.sendNotebookTelemetryActionEvent(TelemetryKeys.NbTelemetryAction.KernelChanged, {
name: kernel.name,
alias: kernelAlias || ''
});
this._kernelChangedEmitter.fire({
newValue: kernel,
oldValue: undefined,