@@ -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,67 +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 . ShowWelcomePage } :executed` ] : WalkthroughContextKeys . GettingStarted ,
34- [ `command:${ Commands . OpenWalkthrough } :executed` ] : WalkthroughContextKeys . GettingStarted ,
35- [ `command:${ Commands . GetStarted } :executed` ] : WalkthroughContextKeys . GettingStarted ,
36-
37- 'graphDetailsView:shown' : WalkthroughContextKeys . VisualizeCodeHistory ,
38- 'graphView:shown' : WalkthroughContextKeys . VisualizeCodeHistory ,
39- 'graphWebview:shown' : WalkthroughContextKeys . VisualizeCodeHistory ,
40- 'commitDetailsView:shown' : WalkthroughContextKeys . VisualizeCodeHistory ,
41- [ `command:${ Commands . ShowGraph } :executed` ] : WalkthroughContextKeys . VisualizeCodeHistory ,
42- [ `command:${ Commands . ShowGraphPage } :executed` ] : WalkthroughContextKeys . VisualizeCodeHistory ,
43- [ `command:${ Commands . ShowGraphView } :executed` ] : WalkthroughContextKeys . VisualizeCodeHistory ,
44- [ `command:${ Commands . ShowInCommitGraph } :executed` ] : WalkthroughContextKeys . VisualizeCodeHistory ,
45- [ `command:${ Commands . ShowInCommitGraphView } :executed` ] : WalkthroughContextKeys . VisualizeCodeHistory ,
46-
47- 'launchpadView:shown' : WalkthroughContextKeys . PrReviews ,
48- 'worktreesView:shown' : WalkthroughContextKeys . PrReviews ,
49- [ `command:${ Commands . ShowLaunchpad } :executed` ] : WalkthroughContextKeys . PrReviews ,
50- [ `command:${ Commands . ShowLaunchpadView } :executed` ] : WalkthroughContextKeys . PrReviews ,
51- [ `command:${ Commands . GitCommandsWorktree } :executed` ] : WalkthroughContextKeys . PrReviews ,
52- [ `command:${ Commands . GitCommandsWorktreeCreate } :executed` ] : WalkthroughContextKeys . PrReviews ,
53- [ `command:${ Commands . GitCommandsWorktreeDelete } :executed` ] : WalkthroughContextKeys . PrReviews ,
54- [ `command:${ Commands . GitCommandsWorktreeOpen } :executed` ] : WalkthroughContextKeys . PrReviews ,
55-
56- [ `command:${ Commands . CreateCloudPatch } :executed` ] : WalkthroughContextKeys . StreamlineCollaboration ,
57- [ `command:${ Commands . CreatePatch } :executed` ] : WalkthroughContextKeys . StreamlineCollaboration ,
58-
59- [ `command:${ Commands . PlusConnectCloudIntegrations } :executed` ] : WalkthroughContextKeys . Integrations ,
60- [ `command:${ Commands . PlusManageCloudIntegrations } :executed` ] : WalkthroughContextKeys . Integrations ,
61- } ;
62102 private readonly _onProgressChanged = new EventEmitter < void > ( ) ;
103+ get onProgressChanged ( ) : Event < void > {
104+ return this . _onProgressChanged . event ;
105+ }
63106
64107 constructor ( private readonly container : Container ) {
65108 this . disposables . push ( this . container . usage . onDidChange ( this . onUsageChanged , this ) ) ;
66- this . walkthroughSize = Object . values ( WalkthroughContextKeys ) . length ;
109+ this . walkthroughSize = walkthroughRequiredMapping . size ;
67110
68111 this . initializeState ( ) ;
69112 }
70113
71114 private initializeState ( ) {
72- for ( const key of Object . values ( WalkthroughContextKeys ) ) {
73- this . state . set ( key , false ) ;
74- }
75- entries ( this . walkthroughByTracking ) . forEach ( ( [ usageKey , walkthroughKey ] ) => {
76- if ( ! this . state . get ( walkthroughKey ) && this . container . usage . isUsed ( usageKey ) ) {
77- void this . completeStep ( walkthroughKey ) ;
115+ for ( const key of walkthroughRequiredMapping . keys ( ) ) {
116+ if ( this . validateStep ( key ) ) {
117+ void this . completeStep ( key ) ;
78118 }
79- } ) ;
119+ }
80120 this . _onProgressChanged . fire ( undefined ) ;
81121 }
82122
@@ -85,25 +125,42 @@ export class WalkthroughStateProvider implements Disposable {
85125 if ( ! usageTrackingKey ) {
86126 return ;
87127 }
88- const walkthroughKey = this . walkthroughByTracking [ usageTrackingKey ] ;
89- if ( walkthroughKey ) {
90- 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 ) {
91146 this . _onProgressChanged . fire ( undefined ) ;
92147 }
93148 }
94149
150+ private _isInitialized : boolean = false ;
151+ private _initPromise : Promise < void > | undefined ;
95152 /**
96153 * Walkthrough view is not ready to listen to context changes immediately after opening VSCode with the walkthrough page opened
97154 * As we're not able to check if the walkthrough is ready, we need to add a delay.
98155 * The 1s delay will not be too annoying for user but it's enough to init
99156 */
100157 private async waitForWalkthroughInitialized ( ) {
101- if ( this . isInitialized ) {
158+ if ( this . _isInitialized ) {
102159 return ;
103160 }
104161 if ( ! this . _initPromise ) {
105162 this . _initPromise = wait ( 1000 ) . then ( ( ) => {
106- this . isInitialized = true ;
163+ this . _isInitialized = true ;
107164 } ) ;
108165 }
109166 await this . _initPromise ;
@@ -115,17 +172,13 @@ export class WalkthroughStateProvider implements Disposable {
115172 * we don't have an ability to reset the flag
116173 */
117174 private async completeStep ( key : WalkthroughContextKeys ) {
118- this . state . set ( key , true ) ;
175+ this . completed . add ( key ) ;
119176 await this . waitForWalkthroughInitialized ( ) ;
120177 void setContext ( `gitlens:walkthroughState:${ key } ` , true ) ;
121178 }
122179
123- get onProgressChanged ( ) : Event < void > {
124- return this . _onProgressChanged . event ;
125- }
126-
127180 get doneCount ( ) {
128- return [ ... this . state . values ( ) ] . filter ( x => x ) . length ;
181+ return this . completed . size ;
129182 }
130183
131184 get progress ( ) {
@@ -135,4 +188,26 @@ export class WalkthroughStateProvider implements Disposable {
135188 dispose ( ) : void {
136189 Disposable . from ( ...this . disposables ) . dispose ( ) ;
137190 }
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+ }
138213}
0 commit comments