Skip to content

Commit d599d41

Browse files
committed
Adds telemetry for walkthrough completion
1 parent 98b7d68 commit d599d41

File tree

4 files changed

+148
-59
lines changed

4 files changed

+148
-59
lines changed

docs/telemetry-events.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1487,7 +1487,15 @@ or
14871487
14881488
```typescript
14891489
{
1490-
'step': 'welcome-in-trial' | 'welcome-paid' | 'welcome-in-trial-expired' | 'get-started-community' | 'visualize-code-history' | 'accelerate-pr-reviews' | 'streamline-collaboration' | 'improve-workflows-with-integrations'
1490+
'step': 'welcome-in-trial' | 'welcome-paid' | 'welcome-in-trial-expired-eligible' | 'welcome-in-trial-expired' | 'get-started-community' | 'visualize-code-history' | 'accelerate-pr-reviews' | 'streamline-collaboration' | 'improve-workflows-with-integrations'
1491+
}
1492+
```
1493+
1494+
### walkthrough/completion
1495+
1496+
```typescript
1497+
{
1498+
'context.key': 'gettingStarted' | 'visualizeCodeHistory' | 'prReviews' | 'streamlineCollaboration' | 'integrations'
14911499
}
14921500
```
14931501

src/constants.telemetry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { CustomEditorTypes, TreeViewTypes, WebviewTypes, WebviewViewTypes }
88
import type { GitContributionTiers } from './git/models/contributor';
99
import type { Period } from './plus/webviews/timeline/protocol';
1010
import type { Flatten } from './system/object';
11+
import type { WalkthroughContextKeys } from './telemetry/walkthroughStateProvider';
1112

1213
export type TelemetryGlobalContext = {
1314
'cloudIntegrations.connected.count': number;
@@ -397,6 +398,9 @@ export type TelemetryEvents = {
397398
walkthrough: {
398399
step?: WalkthroughSteps;
399400
};
401+
'walkthrough/completion': {
402+
'context.key': WalkthroughContextKeys;
403+
};
400404
} & Record<`${WebviewTypes | WebviewViewTypes}/showAborted`, WebviewShownEventData> &
401405
Record<
402406
`${Exclude<WebviewTypes | WebviewViewTypes, 'commitDetails' | 'graph' | 'graphDetails' | 'timeline'>}/shown`,

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ export const urls = Object.freeze({
175175
export type WalkthroughSteps =
176176
| 'welcome-in-trial'
177177
| 'welcome-paid'
178+
| 'welcome-in-trial-expired-eligible'
178179
| 'welcome-in-trial-expired'
179180
| 'get-started-community'
180181
| 'visualize-code-history'

src/telemetry/walkthroughStateProvider.ts

Lines changed: 134 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Disposable, EventEmitter } from 'vscode';
33
import { Commands } from '../constants.commands';
44
import type { TrackedUsageKeys } from '../constants.telemetry';
55
import type { Container } from '../container';
6-
import { entries } from '../system/object';
76
import { wait } from '../system/promise';
87
import { setContext } from '../system/vscode/context';
98
import type { UsageChangeEvent } from './usageTracker';
@@ -16,66 +15,108 @@ export enum WalkthroughContextKeys {
1615
Integrations = 'integrations',
1716
}
1817

18+
type WalkthroughUsage = { states: TrackedUsageKeys[]; usage: TrackedUsageKeys[] };
19+
20+
const walkthroughRequiredMapping: Readonly<Map<WalkthroughContextKeys, WalkthroughUsage>> = new Map<
21+
WalkthroughContextKeys,
22+
WalkthroughUsage
23+
>([
24+
[
25+
WalkthroughContextKeys.GettingStarted,
26+
{
27+
states: [
28+
`command:${Commands.PlusStartPreviewTrial}:executed`,
29+
`command:${Commands.PlusReactivateProTrial}:executed`,
30+
`command:${Commands.OpenWalkthrough}:executed`,
31+
`command:${Commands.GetStarted}:executed`,
32+
],
33+
usage: [],
34+
},
35+
],
36+
[
37+
WalkthroughContextKeys.VisualizeCodeHistory,
38+
{
39+
states: [
40+
`command:${Commands.PlusStartPreviewTrial}:executed`,
41+
`command:${Commands.PlusReactivateProTrial}:executed`,
42+
],
43+
usage: [
44+
'graphDetailsView:shown',
45+
'graphView:shown',
46+
'graphWebview:shown',
47+
'commitDetailsView:shown',
48+
`command:${Commands.ShowGraph}:executed`,
49+
`command:${Commands.ShowGraphPage}:executed`,
50+
`command:${Commands.ShowGraphView}:executed`,
51+
`command:${Commands.ShowInCommitGraph}:executed`,
52+
`command:${Commands.ShowInCommitGraphView}:executed`,
53+
],
54+
},
55+
],
56+
[
57+
WalkthroughContextKeys.PrReviews,
58+
{
59+
states: [
60+
`command:${Commands.PlusStartPreviewTrial}:executed`,
61+
`command:${Commands.PlusReactivateProTrial}:executed`,
62+
],
63+
usage: [
64+
'launchpadView:shown',
65+
'worktreesView:shown',
66+
`command:${Commands.ShowLaunchpad}:executed`,
67+
`command:${Commands.ShowLaunchpadView}:executed`,
68+
`command:${Commands.GitCommandsWorktree}:executed`,
69+
`command:${Commands.GitCommandsWorktreeCreate}:executed`,
70+
`command:${Commands.GitCommandsWorktreeDelete}:executed`,
71+
`command:${Commands.GitCommandsWorktreeOpen}:executed`,
72+
],
73+
},
74+
],
75+
[
76+
WalkthroughContextKeys.StreamlineCollaboration,
77+
{
78+
states: [
79+
`command:${Commands.PlusStartPreviewTrial}:executed`,
80+
`command:${Commands.PlusReactivateProTrial}:executed`,
81+
],
82+
usage: [`command:${Commands.CreateCloudPatch}:executed`, `command:${Commands.CreatePatch}:executed`],
83+
},
84+
],
85+
[
86+
WalkthroughContextKeys.Integrations,
87+
{
88+
states: [],
89+
usage: [
90+
`command:${Commands.PlusConnectCloudIntegrations}:executed`,
91+
`command:${Commands.PlusManageCloudIntegrations}:executed`,
92+
],
93+
},
94+
],
95+
]);
96+
1997
export class WalkthroughStateProvider implements Disposable {
2098
protected disposables: Disposable[] = [];
21-
private readonly state = new Map<WalkthroughContextKeys, boolean>();
99+
private readonly completed = new Set<WalkthroughContextKeys>();
22100
readonly walkthroughSize: number;
23-
private isInitialized = false;
24-
private _initPromise: Promise<void> | undefined;
25101

26-
/**
27-
* using reversed map (instead of direct map as walkthroughToTracking Record<WalkthroughContextKeys, TrackedUsageKeys[]>)
28-
* makes code less readable, but prevents duplicated usageTracker keys
29-
*/
30-
private readonly walkthroughByTracking: Partial<Record<TrackedUsageKeys, WalkthroughContextKeys>> = {
31-
[`command:${Commands.PlusStartPreviewTrial}:executed`]: WalkthroughContextKeys.GettingStarted,
32-
[`command:${Commands.PlusReactivateProTrial}:executed`]: WalkthroughContextKeys.GettingStarted,
33-
[`command:${Commands.OpenWalkthrough}:executed`]: WalkthroughContextKeys.GettingStarted,
34-
[`command:${Commands.GetStarted}:executed`]: WalkthroughContextKeys.GettingStarted,
35-
36-
'graphDetailsView:shown': WalkthroughContextKeys.VisualizeCodeHistory,
37-
'graphView:shown': WalkthroughContextKeys.VisualizeCodeHistory,
38-
'graphWebview:shown': WalkthroughContextKeys.VisualizeCodeHistory,
39-
'commitDetailsView:shown': WalkthroughContextKeys.VisualizeCodeHistory,
40-
[`command:${Commands.ShowGraph}:executed`]: WalkthroughContextKeys.VisualizeCodeHistory,
41-
[`command:${Commands.ShowGraphPage}:executed`]: WalkthroughContextKeys.VisualizeCodeHistory,
42-
[`command:${Commands.ShowGraphView}:executed`]: WalkthroughContextKeys.VisualizeCodeHistory,
43-
[`command:${Commands.ShowInCommitGraph}:executed`]: WalkthroughContextKeys.VisualizeCodeHistory,
44-
[`command:${Commands.ShowInCommitGraphView}:executed`]: WalkthroughContextKeys.VisualizeCodeHistory,
45-
46-
'launchpadView:shown': WalkthroughContextKeys.PrReviews,
47-
'worktreesView:shown': WalkthroughContextKeys.PrReviews,
48-
[`command:${Commands.ShowLaunchpad}:executed`]: WalkthroughContextKeys.PrReviews,
49-
[`command:${Commands.ShowLaunchpadView}:executed`]: WalkthroughContextKeys.PrReviews,
50-
[`command:${Commands.GitCommandsWorktree}:executed`]: WalkthroughContextKeys.PrReviews,
51-
[`command:${Commands.GitCommandsWorktreeCreate}:executed`]: WalkthroughContextKeys.PrReviews,
52-
[`command:${Commands.GitCommandsWorktreeDelete}:executed`]: WalkthroughContextKeys.PrReviews,
53-
[`command:${Commands.GitCommandsWorktreeOpen}:executed`]: WalkthroughContextKeys.PrReviews,
54-
55-
[`command:${Commands.CreateCloudPatch}:executed`]: WalkthroughContextKeys.StreamlineCollaboration,
56-
[`command:${Commands.CreatePatch}:executed`]: WalkthroughContextKeys.StreamlineCollaboration,
57-
58-
[`command:${Commands.PlusConnectCloudIntegrations}:executed`]: WalkthroughContextKeys.Integrations,
59-
[`command:${Commands.PlusManageCloudIntegrations}:executed`]: WalkthroughContextKeys.Integrations,
60-
};
61102
private readonly _onProgressChanged = new EventEmitter<void>();
103+
get onProgressChanged(): Event<void> {
104+
return this._onProgressChanged.event;
105+
}
62106

63107
constructor(private readonly container: Container) {
64108
this.disposables.push(this.container.usage.onDidChange(this.onUsageChanged, this));
65-
this.walkthroughSize = Object.values(WalkthroughContextKeys).length;
109+
this.walkthroughSize = walkthroughRequiredMapping.size;
66110

67111
this.initializeState();
68112
}
69113

70114
private initializeState() {
71-
for (const key of Object.values(WalkthroughContextKeys)) {
72-
this.state.set(key, false);
73-
}
74-
entries(this.walkthroughByTracking).forEach(([usageKey, walkthroughKey]) => {
75-
if (!this.state.get(walkthroughKey) && this.container.usage.isUsed(usageKey)) {
76-
void this.completeStep(walkthroughKey);
115+
for (const key of walkthroughRequiredMapping.keys()) {
116+
if (this.validateStep(key)) {
117+
void this.completeStep(key);
77118
}
78-
});
119+
}
79120
this._onProgressChanged.fire(undefined);
80121
}
81122

@@ -84,25 +125,42 @@ export class WalkthroughStateProvider implements Disposable {
84125
if (!usageTrackingKey) {
85126
return;
86127
}
87-
const walkthroughKey = this.walkthroughByTracking[usageTrackingKey];
88-
if (walkthroughKey) {
89-
void this.completeStep(walkthroughKey);
128+
129+
const stepsToValidate = this.getStepsFromUsage(usageTrackingKey);
130+
let shouldFire = false;
131+
for (const step of stepsToValidate) {
132+
// no need to check if the step is already completed
133+
if (this.completed.has(step)) {
134+
continue;
135+
}
136+
137+
if (this.validateStep(step)) {
138+
void this.completeStep(step);
139+
this.container.telemetry.sendEvent('walkthrough/completion', {
140+
'context.key': step,
141+
});
142+
shouldFire = true;
143+
}
144+
}
145+
if (shouldFire) {
90146
this._onProgressChanged.fire(undefined);
91147
}
92148
}
93149

150+
private _isInitialized: boolean = false;
151+
private _initPromise: Promise<void> | undefined;
94152
/**
95153
* Walkthrough view is not ready to listen to context changes immediately after opening VSCode with the walkthrough page opened
96154
* As we're not able to check if the walkthrough is ready, we need to add a delay.
97155
* The 1s delay will not be too annoying for user but it's enough to init
98156
*/
99157
private async waitForWalkthroughInitialized() {
100-
if (this.isInitialized) {
158+
if (this._isInitialized) {
101159
return;
102160
}
103161
if (!this._initPromise) {
104162
this._initPromise = wait(1000).then(() => {
105-
this.isInitialized = true;
163+
this._isInitialized = true;
106164
});
107165
}
108166
await this._initPromise;
@@ -114,17 +172,13 @@ export class WalkthroughStateProvider implements Disposable {
114172
* we don't have an ability to reset the flag
115173
*/
116174
private async completeStep(key: WalkthroughContextKeys) {
117-
this.state.set(key, true);
175+
this.completed.add(key);
118176
await this.waitForWalkthroughInitialized();
119177
void setContext(`gitlens:walkthroughState:${key}`, true);
120178
}
121179

122-
get onProgressChanged(): Event<void> {
123-
return this._onProgressChanged.event;
124-
}
125-
126180
get doneCount() {
127-
return [...this.state.values()].filter(x => x).length;
181+
return this.completed.size;
128182
}
129183

130184
get progress() {
@@ -134,4 +188,26 @@ export class WalkthroughStateProvider implements Disposable {
134188
dispose(): void {
135189
Disposable.from(...this.disposables).dispose();
136190
}
191+
192+
private getStepsFromUsage(usageKey: TrackedUsageKeys): WalkthroughContextKeys[] {
193+
const keys: WalkthroughContextKeys[] = [];
194+
for (const [key, { states, usage: events }] of walkthroughRequiredMapping) {
195+
if (states.includes(usageKey) || events.includes(usageKey)) {
196+
keys.push(key);
197+
}
198+
}
199+
200+
return keys;
201+
}
202+
203+
private validateStep(key: WalkthroughContextKeys): boolean {
204+
const { states, usage: events } = walkthroughRequiredMapping.get(key)!;
205+
if (states.length && !states.some(state => this.container.usage.isUsed(state))) {
206+
return false;
207+
}
208+
if (events.length && !events.some(event => this.container.usage.isUsed(event))) {
209+
return false;
210+
}
211+
return true;
212+
}
137213
}

0 commit comments

Comments
 (0)