Skip to content

Commit 12f306a

Browse files
authored
Add telemetry for the v2 (#1105)
- `EXTENSION_INITIALIZATION` with `"dabs" | "legacy" | "unknown"` types - `AUTO_LOGIN` with `"init" | "hostChange" | "targetChange"` sources - `MANUAL_LOGIN` with `"authTypeSwitch" | "authTypeLogin" | "command"` sources - `AUTO_MIGRATION` - `MANUAL_MIGRATION` - `BUNDLE_RUN` with `jobs | pipelines` resource type All metrics also have `duration` property
1 parent 8c60fa5 commit 12f306a

File tree

11 files changed

+239
-123
lines changed

11 files changed

+239
-123
lines changed

packages/databricks-vscode/src/bundle/BundleProjectManager.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {ProjectConfigFile} from "../file-managers/ProjectConfigFile";
1717
import {randomUUID} from "crypto";
1818
import {onError} from "../utils/onErrorDecorator";
1919
import {BundleInitWizard, promptToOpenSubProjects} from "./BundleInitWizard";
20+
import {EventReporter, Events, Telemetry} from "../telemetry";
2021

2122
export class BundleProjectManager {
2223
private logger = logging.NamedLogger.getOrCreate(Loggers.Extension);
@@ -42,7 +43,8 @@ export class BundleProjectManager {
4243
private connectionManager: ConnectionManager,
4344
private configModel: ConfigModel,
4445
private bundleFileSet: BundleFileSet,
45-
private workspaceUri: Uri
46+
private workspaceUri: Uri,
47+
private telemetry: Telemetry
4648
) {
4749
this.disposables.push(
4850
this.bundleFileSet.bundleDataCache.onDidChange(async () => {
@@ -82,28 +84,20 @@ export class BundleProjectManager {
8284
if (await this.isBundleProject()) {
8385
return;
8486
}
85-
87+
const recordEvent = this.telemetry.start(
88+
Events.EXTENSION_INITIALIZATION
89+
);
8690
await Promise.all([
8791
// This method updates subProjectsAvailabe context.
8892
// We have a configurationView that shows "openSubProjects" button if the context value is true.
8993
this.detectSubProjects(),
9094
// This method will try to automatically create bundle config if there's existing valid project.json config.
91-
// In the case project.json auth doesn't work, it sets pendingManualMigration context to enable
92-
// configurationView with the configureManualMigration button.
95+
// In the case project.json doesn't exist or its auth doesn't work, it sets pendingManualMigration context
96+
// to enable configurationView with the configureManualMigration button.
9397
this.detectLegacyProjectConfig(),
9498
]);
95-
// This method checks if we are already in a project but don't have a legacy config. In this case, it sets pendingManualMigration
96-
// context to enable configurationView with the configureManualMigration button.
97-
await this.isInProjectWithoutConfig();
98-
}
99-
100-
private async isInProjectWithoutConfig() {
101-
if (
102-
this.legacyProjectConfig === undefined &&
103-
!(await this.isBundleProject())
104-
) {
105-
this.customWhenContext.setPendingManualMigration(true);
106-
}
99+
const type = this.legacyProjectConfig ? "legacy" : "unknown";
100+
recordEvent({success: true, type});
107101
}
108102

109103
private async configureBundleProject() {
@@ -126,9 +120,18 @@ export class BundleProjectManager {
126120
this.logger.debug("Project services have already been initialized");
127121
return;
128122
}
129-
await this.configModel.init();
130-
await this.connectionManager.init();
131-
this.projectServicesReady = true;
123+
const recordEvent = this.telemetry.start(
124+
Events.EXTENSION_INITIALIZATION
125+
);
126+
try {
127+
await this.configModel.init();
128+
await this.connectionManager.init();
129+
this.projectServicesReady = true;
130+
recordEvent({success: true, type: "dabs"});
131+
} catch (e) {
132+
recordEvent({success: false, type: "dabs"});
133+
throw e;
134+
}
132135
}
133136

134137
private async disposeProjectServices() {
@@ -154,14 +157,20 @@ export class BundleProjectManager {
154157
private async detectLegacyProjectConfig() {
155158
this.legacyProjectConfig = await this.loadLegacyProjectConfig();
156159
if (!this.legacyProjectConfig) {
160+
this.customWhenContext.setPendingManualMigration(true);
157161
return;
158162
}
159163
this.logger.debug(
160164
"Detected a legacy project.json, starting automatic migration"
161165
);
166+
const recordEvent = this.telemetry.start(Events.AUTO_MIGRATION);
162167
try {
163-
await this.startAutomaticMigration(this.legacyProjectConfig);
168+
await this.startAutomaticMigration(
169+
this.legacyProjectConfig,
170+
recordEvent
171+
);
164172
} catch (error) {
173+
recordEvent({success: false});
165174
this.customWhenContext.setPendingManualMigration(true);
166175
const message =
167176
"Failed to perform automatic migration to Databricks Asset Bundles.";
@@ -186,14 +195,16 @@ export class BundleProjectManager {
186195
}
187196

188197
private async startAutomaticMigration(
189-
legacyProjectConfig: ProjectConfigFile
198+
legacyProjectConfig: ProjectConfigFile,
199+
recordEvent: EventReporter<Events.AUTO_MIGRATION>
190200
) {
191201
let authProvider = legacyProjectConfig.authProvider;
192202
if (!(await authProvider.check())) {
193203
this.logger.debug(
194204
"Legacy project auth was not successful, showing 'configure' welcome screen"
195205
);
196206
this.customWhenContext.setPendingManualMigration(true);
207+
recordEvent({success: false});
197208
return;
198209
}
199210
if (!(authProvider instanceof ProfileAuthProvider)) {
@@ -209,6 +220,7 @@ export class BundleProjectManager {
209220
authProvider as ProfileAuthProvider,
210221
legacyProjectConfig
211222
);
223+
recordEvent({success: true});
212224
}
213225

214226
@onError({
@@ -217,17 +229,27 @@ export class BundleProjectManager {
217229
},
218230
})
219231
public async startManualMigration() {
220-
const authProvider = await LoginWizard.run(this.cli);
221-
if (
222-
authProvider instanceof ProfileAuthProvider &&
223-
(await authProvider.check())
224-
) {
225-
return this.migrateProjectJsonToBundle(
226-
authProvider,
227-
this.legacyProjectConfig
228-
);
229-
} else {
230-
this.logger.debug("Incorrect auth for the project.json migration");
232+
const recordEvent = this.telemetry.start(Events.MANUAL_MIGRATION);
233+
try {
234+
const authProvider = await LoginWizard.run(this.cli);
235+
if (
236+
authProvider instanceof ProfileAuthProvider &&
237+
(await authProvider.check())
238+
) {
239+
await this.migrateProjectJsonToBundle(
240+
authProvider,
241+
this.legacyProjectConfig
242+
);
243+
recordEvent({success: true});
244+
} else {
245+
recordEvent({success: false});
246+
this.logger.debug(
247+
"Incorrect auth for the project.json migration"
248+
);
249+
}
250+
} catch (e) {
251+
recordEvent({success: false});
252+
throw e;
231253
}
232254
}
233255

packages/databricks-vscode/src/bundle/run/BundleRunStatusManager.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {BundleRunStatus} from "./BundleRunStatus";
77
import {PipelineRunStatus} from "./PipelineRunStatus";
88
import {Resource, ResourceKey} from "../types";
99
import {ConfigModel} from "../../configuration/models/ConfigModel";
10+
import {logging} from "@databricks/databricks-sdk";
11+
import {Loggers} from "../../logger";
1012
/**
1113
* This class monitors the cli bundle run output and record ids for runs. It also polls for status of the these runs.
1214
*/
1315
export class BundleRunStatusManager implements Disposable {
16+
private logger = logging.NamedLogger.getOrCreate(Loggers.Extension);
1417
private disposables: Disposable[] = [];
1518
public readonly runStatuses: Map<string, BundleRunStatus> = new Map();
1619

@@ -93,26 +96,31 @@ export class BundleRunStatusManager implements Disposable {
9396
})
9497
);
9598
this.onDidChangeEmitter.fire();
96-
await this.bundleRunTerminalManager.run(
97-
resourceKey,
98-
(data) => {
99-
remoteRunStatus.parseId(data);
100-
},
101-
async (exitCode) => {
99+
try {
100+
const result = await this.bundleRunTerminalManager.run(
101+
resourceKey,
102+
(data) => remoteRunStatus.parseId(data)
103+
);
104+
if (result.cancelled) {
102105
await remoteRunStatus.cancel();
103-
if (exitCode !== 0) {
104-
remoteRunStatus.runState = "error";
105-
} else {
106-
remoteRunStatus.runState = "cancelled";
107-
}
108106
}
109-
);
107+
return result;
108+
} catch (e) {
109+
// In the case of a failed run cancellation is expected to fail too.
110+
// Because of it we catch errors from the `cancel` and log them, re-throwing original error that will be shown to the user.
111+
await remoteRunStatus.cancel().catch((error) => {
112+
this.logger.error(
113+
"Error while cancelling a run after a failure",
114+
error
115+
);
116+
});
117+
remoteRunStatus.runState = "error";
118+
throw e;
119+
}
110120
}
111121

112122
async cancel(resourceKey: string) {
113-
const runner = this.runStatuses.get(resourceKey);
114123
this.bundleRunTerminalManager.cancel(resourceKey);
115-
await runner?.cancel();
116124
}
117125

118126
dispose() {

packages/databricks-vscode/src/bundle/run/BundleRunTerminalManager.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ export class BundleRunTerminalManager implements Disposable {
2525

2626
async run(
2727
resourceKey: string,
28-
onDidUpdate?: (data: string) => void,
29-
onDidClose?: (code: number | null) => void
30-
) {
28+
onDidUpdate?: (data: string) => void
29+
): Promise<{cancelled: boolean; exitCode?: number | null}> {
3130
const target = this.bundleRemoteStateModel.target;
3231
if (target === undefined) {
3332
throw new Error(`Cannot run ${resourceKey}, Target is undefined`);
@@ -86,26 +85,17 @@ export class BundleRunTerminalManager implements Disposable {
8685
);
8786

8887
// Wait for the process to exit
89-
await new Promise<void>((resolve, reject) => {
90-
if (terminal === undefined) {
91-
resolve();
92-
return;
93-
}
88+
return await new Promise((resolve, reject) => {
9489
terminal.pty.onDidCloseProcess((exitCode) => {
95-
onDidClose?.(exitCode);
9690
if (exitCode === 0 || terminal.pty.isClosed) {
9791
// Resolve when the process exits with code 0 or is closed by human action
98-
resolve();
92+
resolve({cancelled: terminal.pty.isClosed, exitCode});
9993
} else {
10094
reject(
10195
new Error(`Process exited with code ${exitCode}`)
10296
);
10397
}
10498
}, disposables);
105-
window.onDidCloseTerminal((e) => {
106-
// Resolve when the process is closed by human action
107-
e.name === terminal.terminal.name && resolve();
108-
}, disposables);
10999
});
110100
} finally {
111101
disposables.forEach((i) => i.dispose());

packages/databricks-vscode/src/bundle/run/CustomOutputTerminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class CustomOutputTerminal implements Pseudoterminal {
6868
);
6969
}
7070

71-
if (exitCode !== 0) {
71+
if (exitCode !== 0 && exitCode !== null) {
7272
this.writeEmitter.fire(
7373
"\x1b[31mProcess exited with code " +
7474
exitCode +

packages/databricks-vscode/src/configuration/ConnectionCommands.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {ConfigModel} from "./models/ConfigModel";
1515
import {saveNewProfile} from "./LoginWizard";
1616
import {PersonalAccessTokenAuthProvider} from "./auth/AuthProvider";
1717
import {normalizeHost} from "../utils/urlUtils";
18+
import {AUTH_TYPE_SWITCH_ID, AUTH_TYPE_LOGIN_ID} from "./ui/AuthTypeComponent";
19+
import {ManualLoginSource} from "../telemetry/constants";
1820

1921
function formatQuickPickClusterSize(sizeInMB: number): string {
2022
if (sizeInMB > 1024) {
@@ -64,14 +66,20 @@ export class ConnectionCommands implements Disposable {
6466
this.connectionManager.logout();
6567
}
6668

67-
async configureLoginCommand() {
69+
async configureLoginCommand(arg?: {id: string}) {
70+
let source: ManualLoginSource = "command";
71+
if (arg?.id === AUTH_TYPE_SWITCH_ID) {
72+
source = "authTypeSwitch";
73+
} else if (arg?.id === AUTH_TYPE_LOGIN_ID) {
74+
source = "authTypeLogin";
75+
}
6876
await window.withProgress(
6977
{
7078
location: {viewId: "configurationView"},
7179
title: "Configuring Databricks login",
7280
},
7381
async () => {
74-
await this.connectionManager.configureLogin();
82+
await this.connectionManager.configureLogin(source);
7583
}
7684
);
7785
}

0 commit comments

Comments
 (0)