1+ import { md5 } from '@env/crypto' ;
12import slug from 'slug' ;
23import type { QuickPick } from 'vscode' ;
34import { Uri } from 'vscode' ;
@@ -52,11 +53,12 @@ interface Context {
5253
5354interface State {
5455 item ?: StartWorkItem ;
55- action ?: StartWorkAction ;
56+ type ?: StartWorkType ;
5657 inWorktree ?: boolean ;
5758}
5859
59- export type StartWorkAction = 'start' ;
60+ export type StartWorkType = 'branch' | 'branch-worktree' | 'issue' | 'issue-worktree' ;
61+ type StartWorkTypeItem = { type : StartWorkType ; inWorktree ?: boolean } ;
6062
6163export interface StartWorkCommandArgs {
6264 readonly command : 'startWork' ;
@@ -99,30 +101,57 @@ export class StartWorkCommand extends QuickCommand<State> {
99101 connectedIntegrations : await this . getConnectedIntegrations ( ) ,
100102 } ;
101103
102- const opened = false ;
104+ let opened = false ;
103105 while ( this . canStepsContinue ( state ) ) {
106+ const hasConnectedIntegrations = [ ...context . connectedIntegrations . values ( ) ] . some ( c => c ) ;
104107 context . title = this . title ;
105108
106109 if ( state . counter < 1 ) {
107- const result = yield * this . selectCommandStep ( state ) ;
110+ if ( this . container . telemetry . enabled ) {
111+ this . container . telemetry . sendEvent (
112+ opened ? 'startWork/steps/type' : 'startWork/opened' ,
113+ {
114+ ...context . telemetryContext ! ,
115+ connected : hasConnectedIntegrations ,
116+ } ,
117+ this . source ,
118+ ) ;
119+ }
120+
121+ opened = true ;
122+ const result = yield * this . selectTypeStep ( state ) ;
108123 if ( result === StepResultBreak ) continue ;
109- state . action = result . action ;
124+ state . type = result . type ;
110125 state . inWorktree = result . inWorktree ;
126+ if ( this . container . telemetry . enabled ) {
127+ this . container . telemetry . sendEvent (
128+ 'startWork/type/chosen' ,
129+ {
130+ ...context . telemetryContext ! ,
131+ connected : hasConnectedIntegrations ,
132+ type : state . type ,
133+ } ,
134+ this . source ,
135+ ) ;
136+ }
111137 }
112138
113- if ( state . counter < 2 && ! state . action ) {
114- const hasConnectedIntegrations = [ ...context . connectedIntegrations . values ( ) ] . some ( c => c ) ;
139+ if ( ( state . counter < 2 && state . type === 'issue' ) || state . type === 'issue-worktree' ) {
115140 if ( ! hasConnectedIntegrations ) {
116141 if ( this . container . telemetry . enabled ) {
117142 this . container . telemetry . sendEvent (
118143 opened ? 'startWork/steps/connect' : 'startWork/opened' ,
119144 {
120145 ...context . telemetryContext ! ,
121146 connected : false ,
147+ type : state . type ,
122148 } ,
123149 this . source ,
124150 ) ;
125151 }
152+
153+ opened = true ;
154+
126155 const isUsingCloudIntegrations = configuration . get ( 'cloudIntegrations.enabled' , undefined , false ) ;
127156 const result = isUsingCloudIntegrations
128157 ? yield * this . confirmCloudIntegrationsConnectStep ( state , context )
@@ -133,67 +162,99 @@ export class StartWorkCommand extends QuickCommand<State> {
133162 }
134163
135164 await updateContextItems ( this . container , context ) ;
165+ if ( this . container . telemetry . enabled ) {
166+ this . container . telemetry . sendEvent (
167+ opened ? 'startWork/steps/issue' : 'startWork/opened' ,
168+ {
169+ ...context . telemetryContext ! ,
170+ connected : true ,
171+ type : state . type ,
172+ } ,
173+ this . source ,
174+ ) ;
175+ }
176+
177+ opened = true ;
178+
136179 const result = yield * this . pickIssueStep ( state , context ) ;
137180 if ( result === StepResultBreak ) continue ;
138- if ( typeof result !== 'string' ) {
181+ if ( ! isStartWorkTypeItem ( result ) ) {
139182 state . item = result ;
140- state . action = 'start' ;
183+ if ( this . container . telemetry . enabled ) {
184+ this . container . telemetry . sendEvent (
185+ 'startWork/issue/chosen' ,
186+ {
187+ ...context . telemetryContext ! ,
188+ connected : true ,
189+ type : state . type ,
190+ 'item.id' : getStartWorkItemIdHash ( result ) ,
191+ 'item.type' : result . item . issue . type ,
192+ 'item.provider' : result . item . issue . provider . id ,
193+ 'item.assignees.count' : result . item . issue . assignees ?. length ?? undefined ,
194+ 'item.createdDate' : result . item . issue . createdDate . getTime ( ) ,
195+ 'item.updatedDate' : result . item . issue . updatedDate . getTime ( ) ,
196+
197+ 'item.comments.count' : result . item . issue . commentsCount ?? undefined ,
198+ 'item.upvotes.count' : result . item . issue . thumbsUpCount ?? undefined ,
199+
200+ 'item.issue.state' : result . item . issue . state ,
201+ } ,
202+ this . source ,
203+ ) ;
204+ }
141205 } else {
142- state . action = result ;
206+ state . type = result . type ;
207+ state . inWorktree = result . inWorktree ;
143208 }
144209 }
145210
146211 const issue = state . item ?. item ?. issue ;
147212 const repo = issue && ( await this . getIssueRepositoryIfExists ( issue ) ) ;
148213
149- if ( typeof state . action === 'string' ) {
150- switch ( state . action ) {
151- case 'start' : {
152- const result = yield * getSteps (
153- this . container ,
154- {
155- command : 'branch' ,
156- state : {
157- subcommand : 'create' ,
158- repo : repo ,
159- name : issue
160- ? `${ slug ( issue . id , { lower : false } ) } -${ slug ( issue . title ) } `
161- : undefined ,
162- suggestNameOnly : true ,
163- suggestRepoOnly : true ,
164- flags : state . inWorktree ? [ '--worktree' ] : [ '--switch' ] ,
165- } ,
166- confirm : false ,
167- } ,
168- this . pickedVia ,
169- ) ;
170- if ( result === StepResultBreak ) {
171- endSteps ( state ) ;
172- } else {
173- state . counter -- ;
174- state . action = undefined ;
175- }
176- break ;
177- }
178- }
214+ const result = yield * getSteps (
215+ this . container ,
216+ {
217+ command : 'branch' ,
218+ state : {
219+ subcommand : 'create' ,
220+ repo : repo ,
221+ name : issue ? `${ slug ( issue . id , { lower : false } ) } -${ slug ( issue . title ) } ` : undefined ,
222+ suggestNameOnly : true ,
223+ suggestRepoOnly : true ,
224+ flags : state . inWorktree ? [ '--worktree' ] : [ '--switch' ] ,
225+ } ,
226+ confirm : false ,
227+ } ,
228+ this . pickedVia ,
229+ ) ;
230+ if ( result === StepResultBreak ) {
231+ endSteps ( state ) ;
232+ } else {
233+ state . counter -- ;
179234 }
180235 }
181236
182237 return state . counter < 0 ? StepResultBreak : undefined ;
183238 }
184239
185- private * selectCommandStep (
240+ private * selectTypeStep (
186241 state : StepState < State > ,
187- ) : StepResultGenerator < { action ?: StartWorkAction ; inWorktree ?: boolean } > {
242+ ) : StepResultGenerator < { type : StartWorkType ; inWorktree ?: boolean } > {
188243 const step = createPickStep ( {
189244 placeholder : 'Start work by creating a new branch' ,
190245 items : [
191- createQuickPickItemOfT ( 'Create a Branch' , {
192- action : 'start' ,
246+ createQuickPickItemOfT < StartWorkTypeItem > ( 'Create a Branch' , {
247+ type : 'branch' ,
248+ } ) ,
249+ createQuickPickItemOfT < StartWorkTypeItem > ( 'Create a Branch in a Worktree' , {
250+ type : 'branch-worktree' ,
251+ inWorktree : true ,
252+ } ) ,
253+ createQuickPickItemOfT < StartWorkTypeItem > ( 'Create a Branch from an Issue' , { type : 'issue' } ) ,
254+ createQuickPickItemOfT < StartWorkTypeItem > ( 'Create a Branch from an Issue in a Worktree' , {
255+ type : 'issue-worktree' ,
256+ inWorktree : true ,
193257 } ) ,
194- createQuickPickItemOfT ( 'Create a Branch in a Worktree' , { action : 'start' , inWorktree : true } ) ,
195- createQuickPickItemOfT ( 'Create a Branch from an Issue' , { } ) ,
196- createQuickPickItemOfT ( 'Create a Branch from an Issue in a Worktree' , { inWorktree : true } ) ,
197258 ] ,
198259 } ) ;
199260 const selection : StepSelection < typeof step > = yield step ;
@@ -336,7 +397,7 @@ export class StartWorkCommand extends QuickCommand<State> {
336397 private * pickIssueStep (
337398 state : StepState < State > ,
338399 context : Context ,
339- ) : StepResultGenerator < StartWorkItem | StartWorkAction > {
400+ ) : StepResultGenerator < StartWorkItem | StartWorkTypeItem > {
340401 const buildIssueItem = ( i : StartWorkItem ) => {
341402 const buttons = i . item . issue . url ? [ OpenOnGitHubQuickInputButton ] : [ ] ;
342403 return {
@@ -366,15 +427,15 @@ export class StartWorkCommand extends QuickCommand<State> {
366427
367428 function getItemsAndPlaceholder ( ) : {
368429 placeholder : string ;
369- items : QuickPickItemOfT < StartWorkItem | StartWorkAction > [ ] ;
430+ items : QuickPickItemOfT < StartWorkItem | StartWorkTypeItem > [ ] ;
370431 } {
371432 if ( ! context . result . items . length ) {
372433 return {
373434 placeholder : 'No issues found. Start work anyway.' ,
374435 items : [
375- createQuickPickItemOfT (
436+ createQuickPickItemOfT < StartWorkTypeItem > (
376437 state . inWorktree ? 'Create a branch on a worktree' : 'Create a branch' ,
377- 'start' ,
438+ { type : state . inWorktree ? 'branch-worktree' : 'branch' , inWorktree : state . inWorktree } ,
378439 ) ,
379440 ] ,
380441 } ;
@@ -395,7 +456,7 @@ export class StartWorkCommand extends QuickCommand<State> {
395456 matchOnDetail : true ,
396457 items : items ,
397458 onDidClickItemButton : ( _quickpick , button , { item } ) => {
398- if ( button === OpenOnGitHubQuickInputButton && typeof item !== 'string' ) {
459+ if ( button === OpenOnGitHubQuickInputButton && ! isStartWorkTypeItem ( item ) ) {
399460 this . open ( item ) ;
400461 return true ;
401462 }
@@ -411,13 +472,6 @@ export class StartWorkCommand extends QuickCommand<State> {
411472 return typeof element . item === 'string' ? element . item : { ...element . item } ;
412473 }
413474
414- private startWork ( state : PartialStepState < State > , item ?: StartWorkItem ) {
415- state . action = 'start' ;
416- if ( item != null ) {
417- state . item = item ;
418- }
419- }
420-
421475 private open ( item : StartWorkItem ) : void {
422476 if ( item . item . issue . url == null ) return ;
423477 void openUrl ( item . item . issue . url ) ;
@@ -451,4 +505,22 @@ async function updateContextItems(container: Container, context: Context) {
451505 } ) ,
452506 ) ?? [ ] ,
453507 } ;
508+ if ( container . telemetry . enabled ) {
509+ updateTelemetryContext ( context ) ;
510+ }
511+ }
512+
513+ function updateTelemetryContext ( context : Context ) {
514+ context . telemetryContext = {
515+ ...context . telemetryContext ! ,
516+ 'items.count' : context . result . items . length ,
517+ } ;
518+ }
519+
520+ function isStartWorkTypeItem ( item : unknown ) : item is StartWorkTypeItem {
521+ return item != null && typeof item === 'object' && 'type' in item ;
522+ }
523+
524+ export function getStartWorkItemIdHash ( item : StartWorkItem ) {
525+ return md5 ( item . item . issue . id ) ;
454526}
0 commit comments