Skip to content

Commit 5f2a2c8

Browse files
authored
fix(typespec-vscode): ensure operation telemetry result is never undefined (#10527)
## Problem The `start-extension` telemetry event (and any other operation whose callback returned `void`) was always being sent with `result="undefined"` and routed through `sendTelemetryErrorEvent`, so it was incorrectly classified as an error event in App Insights. Root cause: the generic type parameter `T` on `doOperationWithTelemetry` was unconstrained, so the auto-detect block (`if (result) ...`) could not derive a `ResultCode` from a callback returning `void` or `undefined`. The `activate` callback in `extension.ts` returned `Promise<void>`, hitting this case on every successful activation. ## Fix - Constrain the generic in `doOperationWithTelemetry` to `T extends ResultCode | Result<unknown>`. Now callers must return one of those, and the result is always derivable. - Update `activate` to return `ResultCode.Success` so it conforms to the new contract. - Drop the `if (result)` guard around the auto-detect block. If a future caller manages to bypass the type system and return a falsy value, the event will still be sent with `result="undefined"` so the bug is visible rather than silent. ## Verification Walked through all 9 `doOperationWithTelemetry` callsites against their callback return types - every one now returns a `ResultCode` or `Result<...>`. TypeScript build is clean.
1 parent c337f32 commit 5f2a2c8

3 files changed

Lines changed: 18 additions & 12 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "typespec-vscode"
5+
---
6+
7+
Ensure operation telemetry events always carry a valid `result` value. Previously the `start-extension` event (and any other operation whose callback returned `void`) was sent with `result="undefined"` and classified as an error event. The `doOperationWithTelemetry` callback is now constrained to return `ResultCode | Result<...>`, so the result is always derived from the operation's return value.

packages/typespec-vscode/src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ logger.registerLogListener("extension-log", new ExtensionLogListener(outputChann
4848
export async function activate(context: ExtensionContext) {
4949
await telemetryClient.doOperationWithTelemetry(
5050
TelemetryEventName.StartExtension,
51-
async (tel: OperationTelemetryEvent) => {
51+
async (tel: OperationTelemetryEvent): Promise<ResultCode> => {
5252
const stateManager = new ExtensionStateManager(context);
5353
telemetryClient.Initialize(stateManager);
5454
/**
@@ -324,6 +324,7 @@ export async function activate(context: ExtensionContext) {
324324
}
325325
showStartUpMessages(stateManager);
326326
telemetryClient.sendDelayedTelemetryEvents();
327+
return ResultCode.Success;
327328
},
328329
);
329330

packages/typespec-vscode/src/telemetry/telemetry-client.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import pkgJson from "../../package.json" with { type: "json" };
44
import { EmptyGuid } from "../const.js";
55
import { ExtensionStateManager } from "../extension-state-manager.js";
66
import logger from "../log/logger.js";
7-
import { ResultCode } from "../types.js";
7+
import { Result, ResultCode } from "../types.js";
88
import { isWhitespaceStringOrUndefined } from "../utils.js";
99
import {
1010
emptyActivityId,
@@ -108,11 +108,11 @@ export class TelemetryClient {
108108
}
109109
}
110110

111-
public async doOperationWithTelemetry<T>(
111+
public async doOperationWithTelemetry<T extends ResultCode | Result<unknown>>(
112112
eventName: TelemetryEventName,
113113
/**
114-
* The result will be set automatically if the return type is ResultCode or Result<T>
115-
* Otherwise, you can set the result manually by setting the opTelemetryEvent.result
114+
* The operation must return either a {@link ResultCode} or a {@link Result}, and the
115+
* telemetry event's result will be set from it automatically.
116116
*/
117117
operation: (
118118
opTelemetryEvent: OperationTelemetryEvent,
@@ -137,13 +137,11 @@ export class TelemetryClient {
137137
const result = await operation(opTelemetryEvent, (result, delay) =>
138138
sendTelemetryEvent(result, delay),
139139
);
140-
if (result) {
141-
const isResultCode = (v: any) => Object.values(ResultCode).includes(v as ResultCode);
142-
if (isResultCode(result)) {
143-
opTelemetryEvent.result ??= result as ResultCode;
144-
} else if (typeof result === "object" && "code" in result && isResultCode(result.code)) {
145-
opTelemetryEvent.result ??= result.code as ResultCode;
146-
}
140+
const isResultCode = (v: any) => Object.values(ResultCode).includes(v as ResultCode);
141+
if (isResultCode(result)) {
142+
opTelemetryEvent.result ??= result as ResultCode;
143+
} else if (typeof result === "object" && "code" in result && isResultCode(result.code)) {
144+
opTelemetryEvent.result ??= result.code as ResultCode;
147145
}
148146
return result;
149147
} catch (e) {

0 commit comments

Comments
 (0)