Telemetry points for SQL Database Projects extension (#14088)

* Added publish and schema compare telemetry
* Adding telemetry points for add/exclude/delete
* telemetry for validation
* Adding telemetry from project roundtrip updates, editing sqlproj, and db refs
* Changed method for obtaining extension ID during registration
* Fixing test failures, updating ads telemetry package dependency
* replacing action strings with enums
* change database project string actions to enums
* Changed action name to better match dialog
* PR feedback
This commit is contained in:
Benjin Dubishar
2021-01-31 16:38:48 -08:00
committed by GitHub
parent 15fa03876d
commit 1c0259f4c5
19 changed files with 239 additions and 71 deletions

View File

@@ -14,7 +14,7 @@ export interface IProjectProviderRegistry {
* Registers a new project provider
* @param provider The project provider
*/
registerProvider(provider: IProjectProvider): vscode.Disposable;
registerProvider(provider: IProjectProvider, providerId: string): vscode.Disposable;
/**
* Clear the providers

View File

@@ -6,14 +6,14 @@
import { IProjectProvider } from 'dataworkspace';
import * as vscode from 'vscode';
import { IProjectProviderRegistry } from './interfaces';
import { TelemetryReporter, TelemetryViews } from './telemetry';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from './telemetry';
export const ProjectProviderRegistry: IProjectProviderRegistry = new class implements IProjectProviderRegistry {
private _providers = new Array<IProjectProvider>();
private _providerFileExtensionMapping: { [key: string]: IProjectProvider } = {};
private _providerProjectTypeMapping: { [key: string]: IProjectProvider } = {};
registerProvider(provider: IProjectProvider): vscode.Disposable {
registerProvider(provider: IProjectProvider, providerId: string): vscode.Disposable {
this.validateProvider(provider);
this._providers.push(provider);
provider.supportedProjectTypes.forEach(projectType => {
@@ -21,9 +21,9 @@ export const ProjectProviderRegistry: IProjectProviderRegistry = new class imple
this._providerProjectTypeMapping[projectType.id.toUpperCase()] = provider;
});
TelemetryReporter.createActionEvent(TelemetryViews.ProviderRegistration, 'ProviderRegistered')
TelemetryReporter.createActionEvent(TelemetryViews.ProviderRegistration, TelemetryActions.ProviderRegistered)
.withAdditionalProperties({
providerId: provider.providerExtensionId,
providerId: providerId,
extensions: provider.supportedProjectTypes.map(p => p.projectFileExtension).sort().join(', ')
})
.send();

View File

@@ -14,13 +14,6 @@ let packageInfo = utils.getPackageInfo(packageJson)!;
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews {
WorkspaceTreePane = 'WorkspaceTreePane',
OpenExistingDialog = 'OpenExistingDialog',
NewProjectDialog = 'NewProjectDialog',
ProviderRegistration = 'ProviderRegistration'
}
export function calculateRelativity(projectPath: string, workspacePath?: string): string {
workspacePath = workspacePath ?? vscode.workspace.workspaceFile?.fsPath;
@@ -42,3 +35,22 @@ export function calculateRelativity(projectPath: string, workspacePath?: string)
return 'other'; // sibling, cousin, descendant, etc.
}
export enum TelemetryViews {
WorkspaceTreePane = 'WorkspaceTreePane',
OpenExistingDialog = 'OpenExistingDialog',
NewProjectDialog = 'NewProjectDialog',
ProviderRegistration = 'ProviderRegistration'
}
export enum TelemetryActions {
ProviderRegistered = 'ProviderRegistered',
ProjectAddedToWorkspace = 'ProjectAddedToWorkspace',
ProjectRemovedFromWorkspace = 'ProjectRemovedFromWorkspace',
OpeningProject = 'OpeningProject',
NewProjectDialogLaunched = 'NewProjectDialogLaunched',
OpeningWorkspace = 'OpeningWorkspace',
OpenExistingDialogLaunched = 'OpenExistingDialogLaunched',
NewProjectDialogCompleted = 'NewProjectDialogCompleted'
}

View File

@@ -69,11 +69,6 @@ declare module 'dataworkspace' {
* Gets the supported project types
*/
readonly supportedProjectTypes: IProjectType[];
/**
* Gets the extension ID for the project provider
*/
readonly providerExtensionId: string;
}
/**

View File

@@ -13,7 +13,7 @@ import { IProjectType } from 'dataworkspace';
import { directoryExist } from '../common/utils';
import { IconPathHelper } from '../common/iconHelper';
import { defaultProjectSaveLocation } from '../common/projectLocationHelper';
import { TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
class NewProjectDialogModel {
projectTypeId: string = '';
@@ -28,7 +28,7 @@ export class NewProjectDialog extends DialogBase {
super(constants.NewProjectDialogTitle, 'NewProject');
// dialog launched from Welcome message button (only visible when no current workspace) vs. "add project" button
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, 'NewProjectDialogLaunched')
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, TelemetryActions.NewProjectDialogLaunched)
.withAdditionalProperties({ isWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
.send();
}
@@ -66,7 +66,7 @@ export class NewProjectDialog extends DialogBase {
try {
const validateWorkspace = await this.workspaceService.validateWorkspace();
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, 'NewProjectDialogCompleted')
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, TelemetryActions.NewProjectDialogCompleted)
.withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId, workspaceValidationPassed: validateWorkspace.toString() })
.send();
@@ -76,7 +76,7 @@ export class NewProjectDialog extends DialogBase {
}
catch (err) {
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, 'NewProjectDialogErrorThrown')
TelemetryReporter.createErrorEvent(TelemetryViews.NewProjectDialog, TelemetryActions.NewProjectDialogCompleted)
.withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId, error: err?.message ? err.message : err })
.send();

View File

@@ -11,7 +11,7 @@ import * as constants from '../common/constants';
import { IWorkspaceService } from '../common/interfaces';
import { fileExist } from '../common/utils';
import { IconPathHelper } from '../common/iconHelper';
import { calculateRelativity, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { calculateRelativity, TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
export class OpenExistingDialog extends DialogBase {
public _targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined;
@@ -32,7 +32,7 @@ export class OpenExistingDialog extends DialogBase {
super(constants.OpenExistingDialogTitle, 'OpenProject');
// dialog launched from Welcome message button (only visible when no current workspace) vs. "add project" button
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, 'OpenWorkspaceProjectDialogLaunched')
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpenExistingDialogLaunched)
.withAdditionalProperties({ isWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
.send();
}
@@ -69,7 +69,7 @@ export class OpenExistingDialog extends DialogBase {
try {
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
// capture that workspace was selected, also if there's already an open workspace that's being replaced
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, 'OpeningWorkspace')
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningWorkspace)
.withAdditionalProperties({ hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
.send();
@@ -91,7 +91,7 @@ export class OpenExistingDialog extends DialogBase {
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
}
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, 'OpeningProject')
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject)
.withAdditionalProperties(telemetryProps)
.send();

View File

@@ -12,7 +12,7 @@ import * as glob from 'fast-glob';
import { IWorkspaceService } from '../common/interfaces';
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
import Logger from '../common/logger';
import { TelemetryReporter, TelemetryViews, calculateRelativity } from '../common/telemetry';
import { TelemetryReporter, TelemetryViews, calculateRelativity, TelemetryActions } from '../common/telemetry';
const WorkspaceConfigurationName = 'dataworkspace';
const ProjectsConfigurationName = 'projects';
@@ -117,7 +117,7 @@ export class WorkspaceService implements IWorkspaceService {
currentProjects.push(projectFile);
newProjectFileAdded = true;
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, 'ProjectAddedToWorkspace')
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, TelemetryActions.ProjectAddedToWorkspace)
.withAdditionalProperties({
workspaceProjectRelativity: calculateRelativity(projectFile.fsPath),
projectType: path.extname(projectFile.fsPath)
@@ -234,7 +234,7 @@ export class WorkspaceService implements IWorkspaceService {
if (projectIdx !== -1) {
currentProjects.splice(projectIdx, 1);
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, 'ProjectRemovedFromWorkspace')
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, TelemetryActions.ProjectRemovedFromWorkspace)
.withAdditionalProperties({
projectType: path.extname(projectFile.fsPath)
}).send();
@@ -290,7 +290,7 @@ export class WorkspaceService implements IWorkspaceService {
}
if (extension.isActive && extension.exports && !ProjectProviderRegistry.providers.includes(extension.exports)) {
ProjectProviderRegistry.registerProvider(extension.exports);
ProjectProviderRegistry.registerProvider(extension.exports, extension.id);
}
}

View File

@@ -23,7 +23,6 @@ export function createProjectProvider(projectTypes: IProjectType[]): IProjectPro
const treeDataProvider = new MockTreeDataProvider();
const projectProvider: IProjectProvider = {
supportedProjectTypes: projectTypes,
providerExtensionId: 'testProvider',
RemoveProject: (projectFile: vscode.Uri): Promise<void> => {
return Promise.resolve();
},
@@ -64,7 +63,7 @@ suite('ProjectProviderRegistry Tests', function (): void {
}
]);
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider at the beginning of the test');
const disposable1 = ProjectProviderRegistry.registerProvider(provider1);
const disposable1 = ProjectProviderRegistry.registerProvider(provider1, 'test.testProvider');
let providerResult = ProjectProviderRegistry.getProviderByProjectExtension('testproj');
should.equal(providerResult, provider1, 'provider1 should be returned for testproj project type');
// make sure the project type is case-insensitive for getProviderByProjectType method
@@ -73,7 +72,7 @@ suite('ProjectProviderRegistry Tests', function (): void {
providerResult = ProjectProviderRegistry.getProviderByProjectExtension('testproj1');
should.equal(providerResult, provider1, 'provider1 should be returned for testproj1 project type');
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'there should be only one project provider at this time');
const disposable2 = ProjectProviderRegistry.registerProvider(provider2);
const disposable2 = ProjectProviderRegistry.registerProvider(provider2, 'test.testProvider2');
providerResult = ProjectProviderRegistry.getProviderByProjectExtension('sqlproj');
should.equal(providerResult, provider2, 'provider2 should be returned for sqlproj project type');
should.strictEqual(ProjectProviderRegistry.providers.length, 2, 'there should be 2 project providers at this time');
@@ -107,7 +106,7 @@ suite('ProjectProviderRegistry Tests', function (): void {
}
]);
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider at the beginning of the test');
ProjectProviderRegistry.registerProvider(provider);
ProjectProviderRegistry.registerProvider(provider, 'test.testProvider');
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'there should be only one project provider at this time');
ProjectProviderRegistry.clear();
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider after clearing the registry');

View File

@@ -78,7 +78,6 @@ suite('workspaceTreeDataProvider Tests', function (): void {
displayName: 'sql project',
description: ''
}],
providerExtensionId: 'testProvider',
RemoveProject: (projectFile: vscode.Uri): Promise<void> => {
return Promise.resolve();
},