@@ -3,7 +3,6 @@ import { Disposable, EventEmitter } from 'vscode';
33import { Commands } from '../constants.commands' ;
44import type { TrackedUsageKeys } from '../constants.telemetry' ;
55import type { Container } from '../container' ;
6- import { entries } from '../system/object' ;
76import { wait } from '../system/promise' ;
87import { setContext } from '../system/vscode/context' ;
98import 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+
1997export 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