Add more Notebook telemetry events (#14755)

* initial checkin

* telemetry for search in notebooks

* telemetry for move notebook

* address comments

* feedback changes

* fix tests paasing NullAdsTelemetryService

* move changeKernel telemetry higher up

* remove telemetry service

* Fix new Notebook events (#14982)

* Notebook telemetry fixes

* update2

* Move event location

* remove service

* remove unused

Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
Maddy
2021-04-05 16:08:39 -07:00
committed by GitHub
parent d8b2df5dbc
commit 43db30d1da
9 changed files with 61 additions and 18 deletions

View File

@@ -122,7 +122,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
} }
}); });
} }
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.TrustNotebook).send(); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.TrustNotebook);
vscode.window.showInformationMessage(loc.msgBookTrusted); vscode.window.showInformationMessage(loc.msgBookTrusted);
} else { } else {
vscode.window.showInformationMessage(loc.msgBookAlreadyTrusted); vscode.window.showInformationMessage(loc.msgBookAlreadyTrusted);
@@ -134,7 +134,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
let bookPathToUpdate = bookTreeItem.book?.contentPath; let bookPathToUpdate = bookTreeItem.book?.contentPath;
if (bookPathToUpdate) { if (bookPathToUpdate) {
let pinStatusChanged = await this.bookPinManager.pinNotebook(bookTreeItem); let pinStatusChanged = await this.bookPinManager.pinNotebook(bookTreeItem);
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.PinNotebook).send(); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.PinNotebook);
if (pinStatusChanged) { if (pinStatusChanged) {
bookTreeItem.contextValue = 'pinnedNotebook'; bookTreeItem.contextValue = 'pinnedNotebook';
} }
@@ -154,7 +154,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
async createBook(): Promise<void> { async createBook(): Promise<void> {
const dialog = new CreateBookDialog(this.bookTocManager); const dialog = new CreateBookDialog(this.bookTocManager);
dialog.createDialog(); dialog.createDialog();
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.CreateBook).send(); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.CreateBook);
} }
async getSelectionQuickPick(movingElement: BookTreeItem): Promise<quickPickResults> { async getSelectionQuickPick(movingElement: BookTreeItem): Promise<quickPickResults> {
@@ -207,6 +207,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
} }
async editBook(movingElement: BookTreeItem): Promise<void> { async editBook(movingElement: BookTreeItem): Promise<void> {
TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.MoveNotebook);
const selectionResults = await this.getSelectionQuickPick(movingElement); const selectionResults = await this.getSelectionQuickPick(movingElement);
if (selectionResults) { if (selectionResults) {
const pickedSection = selectionResults.quickPickSection; const pickedSection = selectionResults.quickPickSection;
@@ -238,7 +239,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
await this.showPreviewFile(urlToOpen); await this.showPreviewFile(urlToOpen);
} }
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.OpenBook).send(); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.OpenBook);
} catch (e) { } catch (e) {
// if there is an error remove book from context // if there is an error remove book from context
const index = this.books.findIndex(book => book.bookPath === bookPath); const index = this.books.findIndex(book => book.bookPath === bookPath);
@@ -298,7 +299,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
} }
this._onDidChangeTreeData.fire(undefined); this._onDidChangeTreeData.fire(undefined);
} }
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.CloseBook).send(); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.CloseBook);
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(loc.closeBookError(book.root, e instanceof Error ? e.message : e)); vscode.window.showErrorMessage(loc.closeBookError(book.root, e instanceof Error ? e.message : e));
} finally { } finally {
@@ -383,7 +384,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this._visitedNotebooks = this._visitedNotebooks.concat([normalizedResource]); this._visitedNotebooks = this._visitedNotebooks.concat([normalizedResource]);
} }
} }
TelemetryReporter.createActionEvent(BookTelemetryView, NbTelemetryActions.OpenNotebookFromBook); TelemetryReporter.sendActionEvent(BookTelemetryView, NbTelemetryActions.OpenNotebookFromBook);
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(loc.openNotebookError(resource, e instanceof Error ? e.message : e)); vscode.window.showErrorMessage(loc.openNotebookError(resource, e instanceof Error ? e.message : e));
} }

View File

@@ -19,6 +19,7 @@ export enum NbTelemetryActions {
SaveBook = 'BookSaved', SaveBook = 'BookSaved',
CreateBook = 'BookCreated', CreateBook = 'BookCreated',
PinNotebook = 'NotebookPinned', PinNotebook = 'NotebookPinned',
OpenNotebookFromBook = 'NotebookOpenedFromBook' OpenNotebookFromBook = 'NotebookOpenedFromBook',
MoveNotebook = 'MoveNotebook',
} }

View File

@@ -75,12 +75,17 @@ export enum TelemetryAction {
RunQueryString = 'RunQueryString', RunQueryString = 'RunQueryString',
ShowChart = 'ShowChart', ShowChart = 'ShowChart',
StopAgentJob = 'StopAgentJob', StopAgentJob = 'StopAgentJob',
WizardPagesNavigation = 'WizardPagesNavigation' WizardPagesNavigation = 'WizardPagesNavigation',
SearchStarted = 'SearchStarted',
SearchCompleted = 'SearchCompleted'
} }
export enum NbTelemetryAction { export enum NbTelemetryAction {
RunCell = 'RunCell', RunCell = 'RunCell',
RunAll = 'RunNotebook' RunAll = 'RunNotebook',
AddCell = 'AddCell',
KernelChanged = 'KernelChanged',
NewNotebookFromConnections = 'NewNotebookWithConnectionProfile'
} }
export enum TelemetryPropertyName { export enum TelemetryPropertyName {

View File

@@ -28,6 +28,8 @@ import { INotebookService } from 'sql/workbench/services/notebook/browser/notebo
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
const msgLoading = localize('loading', "Loading kernels..."); const msgLoading = localize('loading', "Loading kernels...");
@@ -49,7 +51,8 @@ export class AddCellAction extends Action {
constructor( constructor(
id: string, label: string, cssClass: string, id: string, label: string, cssClass: string,
@INotebookService private _notebookService: INotebookService @INotebookService private _notebookService: INotebookService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) { ) {
super(id, label, cssClass); super(id, label, cssClass);
} }
@@ -71,6 +74,9 @@ export class AddCellAction extends Action {
const index = editor.cells?.findIndex(cell => cell.active) ?? 0; const index = editor.cells?.findIndex(cell => cell.active) ?? 0;
editor.addCell(this.cellType, index); editor.addCell(this.cellType, index);
} }
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.AddCell)
.withAdditionalProperties({ cell_type: this.cellType })
.send();
} }
} }
@@ -212,12 +218,14 @@ export class RunAllCellsAction extends Action {
constructor( constructor(
id: string, label: string, cssClass: string, id: string, label: string, cssClass: string,
@INotificationService private notificationService: INotificationService, @INotificationService private notificationService: INotificationService,
@INotebookService private _notebookService: INotebookService @INotebookService private _notebookService: INotebookService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) { ) {
super(id, label, cssClass); super(id, label, cssClass);
} }
public async run(context: URI): Promise<boolean> { public async run(context: URI): Promise<boolean> {
try { try {
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.RunAll);
const editor = this._notebookService.findNotebookEditor(context); const editor = this._notebookService.findNotebookEditor(context);
await editor.runAllCells(); await editor.runAllCells();
return true; return true;
@@ -372,7 +380,8 @@ const kernelDropdownElementId = 'kernel-dropdown';
export class KernelsDropdown extends SelectBox { export class KernelsDropdown extends SelectBox {
private model: NotebookModel; private model: NotebookModel;
private _showAllKernels: boolean = false; private _showAllKernels: boolean = false;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelReady: Promise<INotebookModel>, @IConfigurationService private _configurationService: IConfigurationService) { constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelReady: Promise<INotebookModel>, @IConfigurationService private _configurationService: IConfigurationService,
) {
super([msgLoading], msgLoading, contextViewProvider, container, { labelText: kernelLabel, labelOnTop: false, ariaLabel: kernelLabel, id: kernelDropdownElementId } as ISelectBoxOptionsWithLabel); super([msgLoading], msgLoading, contextViewProvider, container, { labelText: kernelLabel, labelOnTop: false, ariaLabel: kernelLabel, id: kernelDropdownElementId } as ISelectBoxOptionsWithLabel);
if (modelReady) { if (modelReady) {
@@ -641,13 +650,17 @@ export class NewNotebookAction extends Action {
id: string, id: string,
label: string, label: string,
@ICommandService private commandService: ICommandService, @ICommandService private commandService: ICommandService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService @IObjectExplorerService private objectExplorerService: IObjectExplorerService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) { ) {
super(id, label); super(id, label);
this.class = 'notebook-action new-notebook'; this.class = 'notebook-action new-notebook';
} }
async run(context?: azdata.ObjectExplorerContext): Promise<void> { async run(context?: azdata.ObjectExplorerContext): Promise<void> {
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.NewNotebookFromConnections)
.withConnectionInfo(context?.connectionProfile)
.send();
let connProfile: azdata.IConnectionProfile; let connProfile: azdata.IConnectionProfile;
if (context && context.nodeInfo) { if (context && context.nodeInfo) {
let node = await this.objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath); let node = await this.objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);

View File

@@ -39,6 +39,8 @@ import { NotebookSearchView } from 'sql/workbench/contrib/notebook/browser/noteb
import * as path from 'vs/base/common/path'; import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
export const VIEWLET_ID = 'workbench.view.notebooks'; export const VIEWLET_ID = 'workbench.view.notebooks';
@@ -124,7 +126,8 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
@IMenuService private menuService: IMenuService, @IMenuService private menuService: IMenuService,
@IContextKeyService private contextKeyService: IContextKeyService, @IContextKeyService private contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IFileService private readonly fileService: IFileService @IFileService private readonly fileService: IFileService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
) { ) {
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
@@ -255,6 +258,9 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
onQueryValidationError(err); onQueryValidationError(err);
return; return;
} }
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.TelemetryAction.SearchStarted)
.withAdditionalProperties({ triggeredOnType: triggeredOnType })
.send();
this.validateQuery(query).then(() => { this.validateQuery(query).then(() => {
if (this.views.length > 1) { if (this.views.length > 1) {

View File

@@ -43,6 +43,8 @@ import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchStop
import { Action, IAction } from 'vs/base/common/actions'; import { Action, IAction } from 'vs/base/common/actions';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
const $ = dom.$; const $ = dom.$;
@@ -82,6 +84,7 @@ export class NotebookSearchView extends SearchView {
@IOpenerService openerService: IOpenerService, @IOpenerService openerService: IOpenerService,
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@ICommandService readonly commandService: ICommandService, @ICommandService readonly commandService: ICommandService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
) { ) {
super(options, fileService, editorService, progressService, notificationService, dialogService, contextViewService, instantiationService, viewDescriptorService, configurationService, contextService, searchWorkbenchService, contextKeyService, replaceService, textFileService, preferencesService, themeService, searchHistoryService, contextMenuService, menuService, accessibilityService, keybindingService, storageService, openerService, telemetryService); super(options, fileService, editorService, progressService, notificationService, dialogService, contextViewService, instantiationService, viewDescriptorService, configurationService, contextService, searchWorkbenchService, contextKeyService, replaceService, textFileService, preferencesService, themeService, searchHistoryService, contextMenuService, menuService, accessibilityService, keybindingService, storageService, openerService, telemetryService);
@@ -239,6 +242,7 @@ export class NotebookSearchView extends SearchView {
} }
public startSearch(query: ITextQuery, excludePatternText: string, includePatternText: string, triggeredOnType: boolean, searchWidget: NotebookSearchWidget): Thenable<void> { public startSearch(query: ITextQuery, excludePatternText: string, includePatternText: string, triggeredOnType: boolean, searchWidget: NotebookSearchWidget): Thenable<void> {
let start = new Date().getTime();
let progressComplete: () => void; let progressComplete: () => void;
this.progressService.withProgress({ location: this.getProgressLocation(), delay: triggeredOnType ? 300 : 0 }, _progress => { this.progressService.withProgress({ location: this.getProgressLocation(), delay: triggeredOnType ? 300 : 0 }, _progress => {
return new Promise<void>(resolve => progressComplete = resolve); return new Promise<void>(resolve => progressComplete = resolve);
@@ -253,6 +257,12 @@ export class NotebookSearchView extends SearchView {
}, 2000); }, 2000);
const onComplete = async (completed?: ISearchComplete) => { const onComplete = async (completed?: ISearchComplete) => {
let end = new Date().getTime();
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.TelemetryAction.SearchCompleted)
.withAdditionalProperties({ resultsReturned: completed.results.length })
.withAdditionalMeasurements({ timeTakenMs: end - start })
.send();
clearTimeout(slowTimer); clearTimeout(slowTimer);
this.state = SearchUIState.Idle; this.state = SearchUIState.Idle;

View File

@@ -24,6 +24,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
import { MockQuickInputService } from 'sql/workbench/contrib/notebook/test/common/quickInputServiceMock'; import { MockQuickInputService } from 'sql/workbench/contrib/notebook/test/common/quickInputServiceMock';
class TestClientSession extends ClientSessionStub { class TestClientSession extends ClientSessionStub {
@@ -125,7 +126,7 @@ suite('Notebook Actions', function (): void {
let actualCellType: CellType; let actualCellType: CellType;
let action = new AddCellAction('TestId', 'TestLabel', 'TestClass', mockNotebookService.object); let action = new AddCellAction('TestId', 'TestLabel', 'TestClass', mockNotebookService.object, new NullAdsTelemetryService());
action.cellType = testCellType; action.cellType = testCellType;
// Normal use case // Normal use case
@@ -192,7 +193,7 @@ suite('Notebook Actions', function (): void {
let mockNotification = TypeMoq.Mock.ofType<INotificationService>(TestNotificationService); let mockNotification = TypeMoq.Mock.ofType<INotificationService>(TestNotificationService);
mockNotification.setup(n => n.notify(TypeMoq.It.isAny())); mockNotification.setup(n => n.notify(TypeMoq.It.isAny()));
let action = new RunAllCellsAction('TestId', 'TestLabel', 'TestClass', mockNotification.object, mockNotebookService.object); let action = new RunAllCellsAction('TestId', 'TestLabel', 'TestClass', mockNotification.object, mockNotebookService.object, new NullAdsTelemetryService());
// Normal use case // Normal use case
mockNotebookEditor.setup(c => c.runAllCells()).returns(() => Promise.resolve(true)); mockNotebookEditor.setup(c => c.runAllCells()).returns(() => Promise.resolve(true));
@@ -252,7 +253,7 @@ suite('Notebook Actions', function (): void {
return Promise.resolve(true); return Promise.resolve(true);
}); });
let action = new NewNotebookAction('TestId', 'TestLabel', mockCommandService.object, undefined); let action = new NewNotebookAction('TestId', 'TestLabel', mockCommandService.object, undefined, new NullAdsTelemetryService());
action.run(undefined); action.run(undefined);
assert.strictEqual(actualCmdId, NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID); assert.strictEqual(actualCmdId, NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID);

View File

@@ -20,6 +20,6 @@ export class ModelFactory implements IModelFactory {
} }
public createClientSession(options: IClientSessionOptions): IClientSession { public createClientSession(options: IClientSessionOptions): IClientSession {
return new ClientSession(options); return this.instantiationService.createInstance(ClientSession, options);
} }
} }

View File

@@ -971,6 +971,12 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (kernel.info) { if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info); this.updateLanguageInfo(kernel.info.language_info);
} }
this.adstelemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.KernelChanged)
.withAdditionalProperties({
name: kernel.name,
alias: kernelAlias || ''
})
.send();
this._kernelChangedEmitter.fire({ this._kernelChangedEmitter.fire({
newValue: kernel, newValue: kernel,
oldValue: undefined, oldValue: undefined,