Skip to content

Commit 97e520c

Browse files
osortegaCopilot
andcommitted
sessions: add telemetry for Agents window interactions
Add GDPR-compliant telemetry events for user interactions in the Agents window. All event types and logging helpers are centralized in sessionsTelemetry.ts. Titlebar buttons (vscodeAgents.interaction): newSession, runPrimaryTask, addTask, generateNewTask, openTerminal, openInVSCode. Changes panel: togglePanel, versionModeChange, fileSelect, reviewCommentAdded. Fixes microsoft/vscode-internalbacklog#7256 Fixes microsoft/vscode-internalbacklog#7257 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8910ce4 commit 97e520c

8 files changed

Lines changed: 141 additions & 3 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
7+
8+
// --- Titlebar button interactions ---
9+
10+
export type SessionsInteractionButton =
11+
| 'newSession'
12+
| 'runPrimaryTask'
13+
| 'addTask'
14+
| 'generateNewTask'
15+
| 'openTerminal'
16+
| 'openInVSCode';
17+
18+
type SessionsInteractionEvent = {
19+
button: string;
20+
};
21+
22+
type SessionsInteractionClassification = {
23+
owner: 'osortega';
24+
comment: 'Tracks user interactions with buttons in the Agents window';
25+
button: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the button that was clicked' };
26+
};
27+
28+
/**
29+
* Log a titlebar button interaction in the Agents window.
30+
*/
31+
export function logSessionsInteraction(telemetryService: ITelemetryService, button: SessionsInteractionButton): void {
32+
telemetryService.publicLog2<SessionsInteractionEvent, SessionsInteractionClassification>('vscodeAgents.interaction', { button });
33+
}
34+
35+
// --- Changes panel interactions ---
36+
37+
type ChangesViewTogglePanelEvent = {
38+
visible: boolean;
39+
};
40+
41+
type ChangesViewTogglePanelClassification = {
42+
owner: 'osortega';
43+
comment: 'Tracks when the user toggles the Changes panel open or closed.';
44+
visible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the Changes panel is now visible.' };
45+
};
46+
47+
export function logChangesViewToggle(telemetryService: ITelemetryService, visible: boolean): void {
48+
telemetryService.publicLog2<ChangesViewTogglePanelEvent, ChangesViewTogglePanelClassification>('vscodeAgents.changesView/togglePanel', { visible });
49+
}
50+
51+
type ChangesViewVersionModeChangeEvent = {
52+
mode: string;
53+
};
54+
55+
type ChangesViewVersionModeChangeClassification = {
56+
owner: 'osortega';
57+
comment: 'Tracks when the user switches the version mode in the Changes panel (Branch Changes, All Changes, Last Turn).';
58+
mode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The version mode selected by the user.' };
59+
};
60+
61+
export function logChangesViewVersionModeChange(telemetryService: ITelemetryService, mode: string): void {
62+
telemetryService.publicLog2<ChangesViewVersionModeChangeEvent, ChangesViewVersionModeChangeClassification>('vscodeAgents.changesView/versionModeChange', { mode });
63+
}
64+
65+
type ChangesViewFileSelectEvent = {
66+
changeType: string;
67+
};
68+
69+
type ChangesViewFileSelectClassification = {
70+
owner: 'osortega';
71+
comment: 'Tracks when the user selects a changed file in the Changes panel.';
72+
changeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of change (added, modified, deleted).' };
73+
};
74+
75+
export function logChangesViewFileSelect(telemetryService: ITelemetryService, changeType: string): void {
76+
telemetryService.publicLog2<ChangesViewFileSelectEvent, ChangesViewFileSelectClassification>('vscodeAgents.changesView/fileSelect', { changeType });
77+
}
78+
79+
type ChangesViewReviewCommentAddedEvent = {
80+
hasExistingFeedback: boolean;
81+
hasSuggestion: boolean;
82+
isFromPRReview: boolean;
83+
};
84+
85+
type ChangesViewReviewCommentAddedClassification = {
86+
owner: 'osortega';
87+
comment: 'Tracks when a user adds a review comment (feedback) to a file in the Changes panel.';
88+
hasExistingFeedback: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether there was already feedback on this file.' };
89+
hasSuggestion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the feedback includes a code suggestion.' };
90+
isFromPRReview: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the feedback was converted from a PR review comment.' };
91+
};
92+
93+
export function logChangesViewReviewCommentAdded(telemetryService: ITelemetryService, data: { hasExistingFeedback: boolean; hasSuggestion: boolean; isFromPRReview: boolean }): void {
94+
telemetryService.publicLog2<ChangesViewReviewCommentAddedEvent, ChangesViewReviewCommentAddedClassification>('vscodeAgents.changesView/reviewCommentAdded', data);
95+
}

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/c
2020
import { ICommandService } from '../../../../platform/commands/common/commands.js';
2121
import { ILogService } from '../../../../platform/log/common/log.js';
2222
import { ICodeReviewSuggestion } from '../../codeReview/browser/codeReviewService.js';
23+
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
24+
import { logChangesViewReviewCommentAdded } from '../../../common/sessionsTelemetry.js';
2325

2426
// --- Types --------------------------------------------------------------------
2527

@@ -144,6 +146,7 @@ export class AgentFeedbackService extends Disposable implements IAgentFeedbackSe
144146
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
145147
@ICommandService private readonly _commandService: ICommandService,
146148
@ILogService private readonly _logService: ILogService,
149+
@ITelemetryService private readonly _telemetryService: ITelemetryService,
147150
) {
148151
super();
149152
}
@@ -201,6 +204,12 @@ export class AgentFeedbackService extends Disposable implements IAgentFeedbackSe
201204

202205
this._onDidChangeFeedback.fire({ sessionResource, feedbackItems });
203206

207+
logChangesViewReviewCommentAdded(this._telemetryService, {
208+
hasExistingFeedback: hasExistingForFile,
209+
hasSuggestion: !!suggestion,
210+
isFromPRReview: !!sourcePRReviewCommentId,
211+
});
212+
204213
return feedback;
205214
}
206215

src/vs/sessions/contrib/changes/browser/changesTitleBarWidget.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import { IsAuxiliaryWindowContext, AuxiliaryBarVisibleContext } from '../../../.
2424
import { getAgentChangesSummary } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
2525
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
2626
import { IPaneCompositePartService } from '../../../../workbench/services/panecomposite/browser/panecomposite.js';
27+
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2728
import { ViewContainerLocation } from '../../../../workbench/common/views.js';
2829
import { Menus } from '../../../browser/menus.js';
2930
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
31+
import { logChangesViewToggle } from '../../../common/sessionsTelemetry.js';
3032
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
3133
import { CHANGES_VIEW_CONTAINER_ID } from './changesView.js';
3234

@@ -182,11 +184,14 @@ registerAction2(class extends Action2 {
182184
run(accessor: ServicesAccessor): void {
183185
const layoutService = accessor.get(IWorkbenchLayoutService);
184186
const paneCompositeService = accessor.get(IPaneCompositePartService);
187+
const telemetryService = accessor.get(ITelemetryService);
185188

186189
const isVisible = !layoutService.isVisible(Parts.AUXILIARYBAR_PART);
187190
layoutService.setPartHidden(!isVisible, Parts.AUXILIARYBAR_PART);
188191
if (isVisible) {
189192
paneCompositeService.openPaneComposite(CHANGES_VIEW_CONTAINER_ID, ViewContainerLocation.AuxiliaryBar);
190193
}
194+
195+
logChangesViewToggle(telemetryService, isVisible);
191196
}
192197
});

src/vs/sessions/contrib/changes/browser/changesView.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import { IView, Sizing, SplitView } from '../../../../base/browser/ui/splitview/
7474
import { Color } from '../../../../base/common/color.js';
7575
import { PANEL_SECTION_BORDER } from '../../../../workbench/common/theme.js';
7676
import { EditorResourceAccessor, SideBySideEditor } from '../../../../workbench/common/editor.js';
77+
import { logChangesViewFileSelect, logChangesViewVersionModeChange } from '../../../common/sessionsTelemetry.js';
7778

7879
const $ = dom.$;
7980

@@ -551,7 +552,8 @@ export class ChangesViewPane extends ViewPane {
551552
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
552553
@ILabelService private readonly labelService: ILabelService,
553554
@ICodeReviewService private readonly codeReviewService: ICodeReviewService,
554-
@IGitHubService private readonly gitHubService: IGitHubService
555+
@IGitHubService private readonly gitHubService: IGitHubService,
556+
@ITelemetryService private readonly telemetryService: ITelemetryService,
555557
) {
556558
super({ ...options, titleMenuId: MenuId.ChatEditingSessionTitleToolbar }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
557559

@@ -1119,6 +1121,8 @@ export class ChangesViewPane extends ViewPane {
11191121
return;
11201122
}
11211123

1124+
logChangesViewFileSelect(this.telemetryService, e.element.changeType);
1125+
11221126
const items = combinedEntriesObs.get();
11231127
openFileItem(e.element, items, e.sideBySide, !!e.editorOptions?.preserveFocus, !!e.editorOptions?.pinned, items.length > 1);
11241128
}));
@@ -1810,7 +1814,7 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem {
18101814
@IKeybindingService keybindingService: IKeybindingService,
18111815
@IContextKeyService contextKeyService: IContextKeyService,
18121816
@ISessionsManagementService sessionManagementService: ISessionsManagementService,
1813-
@ITelemetryService telemetryService: ITelemetryService,
1817+
@ITelemetryService private readonly telemetryService: ITelemetryService,
18141818
) {
18151819
const actionProvider: IActionWidgetDropdownActionProvider = {
18161820
getActions: () => {
@@ -1829,6 +1833,7 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem {
18291833
category: { label: 'changes', order: 1, showHeader: false },
18301834
run: async () => {
18311835
viewModel.setVersionMode(ChangesVersionMode.BranchChanges);
1836+
logChangesViewVersionModeChange(this.telemetryService, ChangesVersionMode.BranchChanges);
18321837
if (this.element) {
18331838
this.renderLabel(this.element);
18341839
}
@@ -1845,6 +1850,7 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem {
18451850
viewModel.activeSessionLastCheckpointRefObs.get() !== undefined,
18461851
run: async () => {
18471852
viewModel.setVersionMode(ChangesVersionMode.AllChanges);
1853+
logChangesViewVersionModeChange(this.telemetryService, ChangesVersionMode.AllChanges);
18481854
if (this.element) {
18491855
this.renderLabel(this.element);
18501856
}
@@ -1861,6 +1867,7 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem {
18611867
viewModel.activeSessionLastCheckpointRefObs.get() !== undefined,
18621868
run: async () => {
18631869
viewModel.setVersionMode(ChangesVersionMode.LastTurn);
1870+
logChangesViewVersionModeChange(this.telemetryService, ChangesVersionMode.LastTurn);
18641871
if (this.element) {
18651872
this.renderLabel(this.element);
18661873
}

src/vs/sessions/contrib/chat/browser/chat.contribution.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { Schemas } from '../../../../base/common/network.js';
1212
import { URI } from '../../../../base/common/uri.js';
1313
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
1414
import { IProductService } from '../../../../platform/product/common/productService.js';
15+
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
16+
import { logSessionsInteraction } from '../../../common/sessionsTelemetry.js';
1517
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
1618
import { IViewContainersRegistry, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions, WindowVisibility } from '../../../../workbench/common/views.js';
1719
import { Registry } from '../../../../platform/registry/common/platform.js';
@@ -60,6 +62,9 @@ export class OpenSessionWorktreeInVSCodeAction extends Action2 {
6062
}
6163

6264
override async run(accessor: ServicesAccessor): Promise<void> {
65+
const telemetryService = accessor.get(ITelemetryService);
66+
logSessionsInteraction(telemetryService, 'openInVSCode');
67+
6368
const openerService = accessor.get(IOpenerService);
6469
const productService = accessor.get(IProductService);
6570
const sessionsManagementService = accessor.get(ISessionsManagementService);

src/vs/sessions/contrib/chat/browser/runScriptAction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keyb
2626
import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';
2727
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2828
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
29+
import { logSessionsInteraction } from '../../../common/sessionsTelemetry.js';
2930
import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js';
3031
import { SessionsCategories } from '../../../common/categories.js';
3132
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
@@ -121,6 +122,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
121122
@ISessionsConfigurationService private readonly _sessionsConfigService: ISessionsConfigurationService,
122123
@IActionViewItemService private readonly _actionViewItemService: IActionViewItemService,
123124
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
125+
@ITelemetryService private readonly _telemetryService: ITelemetryService,
124126
) {
125127
super();
126128

@@ -194,6 +196,8 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
194196
return;
195197
}
196198

199+
logSessionsInteraction(that._telemetryService, 'runPrimaryTask');
200+
197201
const { tasks, session } = activeState;
198202
if (tasks.length === 0) {
199203
const task = await that._showConfigureQuickPick(session);
@@ -238,6 +242,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
238242
}
239243

240244
async run(): Promise<void> {
245+
logSessionsInteraction(that._telemetryService, 'addTask');
241246
const task = await that._showConfigureQuickPick(session);
242247
if (task) {
243248
await that._sessionsConfigService.runTask(task, session);
@@ -261,6 +266,7 @@ export class RunScriptContribution extends Disposable implements IWorkbenchContr
261266
}
262267

263268
async run(): Promise<void> {
269+
logSessionsInteraction(that._telemetryService, 'generateNewTask');
264270
await that._sessionManagementService.sendAndCreateChat(session, { query: '/generate-run-commands' });
265271
}
266272
}));

src/vs/sessions/contrib/sessions/browser/views/sessionsView.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import { Action2, MenuId, registerAction2 } from '../../../../../platform/action
3030
import { Button } from '../../../../../base/browser/ui/button/button.js';
3131
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
3232
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
33+
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
3334
import { IHostService } from '../../../../../workbench/services/host/browser/host.js';
35+
import { logSessionsInteraction } from '../../../../common/sessionsTelemetry.js';
3436

3537
const $ = DOM.$;
3638
export const SessionsViewId = 'sessions.workbench.view.sessionsView';
@@ -70,6 +72,7 @@ export class SessionsView extends ViewPane {
7072
@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,
7173
@IHostService private readonly hostService: IHostService,
7274
@IStorageService private readonly storageService: IStorageService,
75+
@ITelemetryService private readonly telemetryService: ITelemetryService,
7376
) {
7477
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
7578

@@ -133,7 +136,10 @@ export class SessionsView extends ViewPane {
133136
supportIcons: true,
134137
}));
135138
newSessionButton.label = `$(${Codicon.plus.id}) ${localize('sessionLabel', "Session")}`;
136-
this._register(newSessionButton.onDidClick(() => this.sessionsManagementService.openNewSessionView()));
139+
this._register(newSessionButton.onDidClick(() => {
140+
logSessionsInteraction(this.telemetryService, 'newSession');
141+
this.sessionsManagementService.openNewSessionView();
142+
}));
137143

138144
const buttonLabel = $('.new-session-button-label');
139145
const keybindingHint = $('span.new-session-keybinding-hint');

src/vs/sessions/contrib/terminal/browser/sessionsTerminalContribution.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import { ISessionsManagementService } from '../../sessions/browser/sessionsManag
2020
import { ISession } from '../../sessions/common/sessionData.js';
2121
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
2222
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
23+
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2324
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
25+
import { logSessionsInteraction } from '../../../common/sessionsTelemetry.js';
2426
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
2527
import { TERMINAL_VIEW_ID } from '../../../../workbench/contrib/terminal/common/terminal.js';
2628
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
@@ -319,6 +321,9 @@ class OpenSessionInTerminalAction extends Action2 {
319321
}
320322

321323
override async run(_accessor: ServicesAccessor): Promise<void> {
324+
const telemetryService = _accessor.get(ITelemetryService);
325+
logSessionsInteraction(telemetryService, 'openTerminal');
326+
322327
const layoutService = _accessor.get(IWorkbenchLayoutService);
323328
const viewsService = _accessor.get(IViewsService);
324329

0 commit comments

Comments
 (0)