Skip to content

Commit b97eba7

Browse files
Improves Start Work flows - closes #3832, #3833, and #3836
- Adds title button to connect additional integrations. - Adds telemetry event startWork/title/action - Separates out "create branch" logic from Start Work and moves into its own telemetry even home/createBranch - Removes all references to "start work type" including in telemetry - Updates placeholder messaging - Adds option to connect additional integrations or manage integrations when no issues are found. - Adds telemetry event startWork/action - Fixes back button and prevents refetching issues when clicked - Updates freeze/resume logic into shared place and applies to all areas that use it - Removes launchpad description item when at least one integration is connected in the connect step
1 parent 77d2e9d commit b97eba7

File tree

12 files changed

+339
-334
lines changed

12 files changed

+339
-334
lines changed

src/commands/git/search.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,7 @@ import type {
2828
StepSelection,
2929
StepState,
3030
} from '../quickCommand';
31-
import {
32-
canPickStepContinue,
33-
createPickStep,
34-
endSteps,
35-
freezeStep,
36-
QuickCommand,
37-
StepResultBreak,
38-
} from '../quickCommand';
31+
import { canPickStepContinue, createPickStep, endSteps, QuickCommand, StepResultBreak } from '../quickCommand';
3932
import {
4033
MatchAllToggleQuickInputButton,
4134
MatchCaseToggleQuickInputButton,
@@ -470,7 +463,7 @@ async function updateSearchQuery(
470463
let append = false;
471464

472465
if (usePickers?.author && item.item === 'author:') {
473-
using frozen = freezeStep(step, quickpick);
466+
using _frozen = step.freeze?.();
474467

475468
const authors = ops.get('author:');
476469

@@ -492,8 +485,6 @@ async function updateSearchQuery(
492485
},
493486
);
494487

495-
frozen[Symbol.dispose]();
496-
497488
if (contributors != null) {
498489
const authors = contributors
499490
.map(c => c.email ?? c.name ?? c.username)
@@ -507,7 +498,7 @@ async function updateSearchQuery(
507498
append = true;
508499
}
509500
} else if (usePickers?.file && item.item === 'file:') {
510-
using frozen = freezeStep(step, quickpick);
501+
using _frozen = step.freeze?.();
511502

512503
let files = ops.get('file:');
513504

@@ -520,8 +511,6 @@ async function updateSearchQuery(
520511
defaultUri: state.repo.folder?.uri,
521512
});
522513

523-
frozen[Symbol.dispose]();
524-
525514
if (uris?.length) {
526515
if (files == null) {
527516
files = new Set();

src/commands/quickCommand.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export interface QuickPickStep<T extends QuickPickItem = QuickPickItem> {
6666
value?: string;
6767
selectValueWhenShown?: boolean;
6868

69+
quickpick?: QuickPick<DirectiveQuickPickItem | T>;
70+
freeze?: () => Disposable;
6971
frozen?: boolean;
7072

7173
onDidActivate?(quickpick: QuickPick<DirectiveQuickPickItem | T>): void;
@@ -361,7 +363,28 @@ export function createInputStep<T extends string>(step: Optional<QuickInputStep<
361363
}
362364

363365
export function createPickStep<T extends QuickPickItem>(step: Optional<QuickPickStep<T>, 'type'>): QuickPickStep<T> {
364-
return { type: 'pick', ...step };
366+
const original = step.onDidActivate;
367+
step = { type: 'pick' as const, ...step };
368+
step.onDidActivate = qp => {
369+
step.quickpick = qp;
370+
step.freeze = () => {
371+
qp.enabled = false;
372+
const originalFocusOut = qp.ignoreFocusOut;
373+
qp.ignoreFocusOut = true;
374+
step.frozen = true;
375+
return {
376+
[Symbol.dispose]: () => {
377+
step.frozen = false;
378+
qp.enabled = true;
379+
qp.ignoreFocusOut = originalFocusOut;
380+
qp.show();
381+
},
382+
};
383+
};
384+
original?.(qp);
385+
};
386+
387+
return step as QuickPickStep<T>;
365388
}
366389

367390
export function createCustomStep<T>(step: Optional<CustomStep<T>, 'type'>): CustomStep<T> {
@@ -372,18 +395,6 @@ export function endSteps(state: PartialStepState) {
372395
state.counter = -1;
373396
}
374397

375-
export function freezeStep(step: QuickPickStep, quickpick: QuickPick<any>): Disposable {
376-
quickpick.enabled = false;
377-
step.frozen = true;
378-
return {
379-
[Symbol.dispose]: () => {
380-
step.frozen = false;
381-
quickpick.enabled = true;
382-
quickpick.show();
383-
},
384-
};
385-
}
386-
387398
export interface CrossCommandReference<T = unknown> {
388399
command: Commands;
389400
args?: T;

src/constants.commands.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ export type CoreGitCommands =
329329
| 'git.pushForce'
330330
| 'git.undoCommit';
331331

332+
export type CustomViewCommands =
333+
| 'gitlens.home.openInGraph'
334+
| 'gitlens.home.fetch'
335+
| 'gitlens.home.openPullRequestChanges'
336+
| 'gitlens.home.openPullRequestOnRemote'
337+
| 'gitlens.home.createPullRequest'
338+
| 'gitlens.home.openWorktree'
339+
| 'gitlens.home.switchToBranch'
340+
| 'gitlens.home.createBranch';
341+
332342
export type TreeViewCommands = `gitlens.views.${
333343
| `branches.${
334344
| 'copy'

src/constants.telemetry.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { CustomEditorTypes, TreeViewTypes, WebviewTypes, WebviewViewTypes }
88
import type { FeaturePreviews, FeaturePreviewStatus } from './features';
99
import type { GitContributionTiers } from './git/models/contributor';
1010
import type { Subscription, SubscriptionAccount } from './plus/gk/account/subscription';
11-
import type { StartWorkType } from './plus/startWork/startWork';
1211
import type { GraphColumnConfig } from './plus/webviews/graph/protocol';
1312
import type { Period } from './plus/webviews/timeline/protocol';
1413
import type { Flatten } from './system/object';
@@ -230,6 +229,9 @@ export type TelemetryEvents = {
230229
enabled: boolean;
231230
version: string;
232231
};
232+
/** Temporary - sent when the user chooses to create a branch from the home view */
233+
/** TODO: Replace this once we have source on all git commands*/
234+
'home/createBranch': void;
233235

234236
/** Sent when the Commit Graph is shown */
235237
'timeline/shown': WebviewShownEventData & TimelineShownEventData;
@@ -327,32 +329,37 @@ export type TelemetryEvents = {
327329
'startWork/opened': StartWorkEventData & {
328330
connected: boolean;
329331
};
330-
/** Sent when the user chooses an option to start work in the first step */
331-
'startWork/type/chosen': StartWorkEventData & {
332-
connected: boolean;
333-
type: StartWorkType;
334-
};
335332
/** Sent when the user takes an action on a StartWork issue */
336333
'startWork/issue/action': StartWorkEventData & {
337334
action: 'soft-open';
338335
connected: boolean;
339-
type: StartWorkType;
340336
} & Partial<Record<`item.${string}`, string | number | boolean>>;
341337
/** Sent when the user chooses an issue to start work in the second step */
342338
'startWork/issue/chosen': StartWorkEventData & {
343339
connected: boolean;
344-
type: StartWorkType;
345340
} & Partial<Record<`item.${string}`, string | number | boolean>>;
346341
/** Sent when the Start Work has "reloaded" (while open, e.g. user refreshed or back button) and is disconnected; use `instance` to correlate a Start Work "session" */
347342
'startWork/steps/type': StartWorkEventData & {
348343
connected: boolean;
349344
};
345+
/** Sent when the user reaches the "connect an integration" step of Start Work */
350346
'startWork/steps/connect': StartWorkEventData & {
351347
connected: boolean;
352348
};
349+
/** Sent when the user reaches the "choose an issue" step of Start Work */
353350
'startWork/steps/issue': StartWorkEventData & {
354351
connected: boolean;
355352
};
353+
/** Sent when the user chooses to connect an integration */
354+
'startWork/title/action': StartWorkEventData & {
355+
connected: boolean;
356+
action: 'connect';
357+
};
358+
/** Sent when the user chooses to manage integrations */
359+
'startWork/action': StartWorkEventData & {
360+
connected: boolean;
361+
action: 'manage' | 'connect';
362+
};
356363

357364
/** Sent when a PR review was started in the inspect overview */
358365
openReviewMode: {
@@ -523,7 +530,7 @@ export type StartWorkTelemetryContext = StartWorkEventData;
523530

524531
type StartWorkEventDataBase = {
525532
instance: number;
526-
} & Partial<{ type: StartWorkType }>;
533+
};
527534

528535
type StartWorkEventData = StartWorkEventDataBase & Partial<{ 'items.count': number }>;
529536

src/plus/launchpad/launchpad.ts

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
canPickStepContinue,
1414
createPickStep,
1515
endSteps,
16-
freezeStep,
1716
QuickCommand,
1817
StepResultBreak,
1918
} from '../../commands/quickCommand';
@@ -978,20 +977,23 @@ export class LaunchpadCommand extends QuickCommand<State> {
978977
state: StepState<State>,
979978
context: Context,
980979
): AsyncStepResultGenerator<{ connected: boolean | IntegrationId; resume: () => void }> {
981-
const confirmations: (QuickPickItemOfT<IntegrationId> | DirectiveQuickPickItem)[] = [
982-
createDirectiveQuickPickItem(Directive.Cancel, undefined, {
983-
label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked',
984-
detail: 'Click to learn more about Launchpad',
985-
iconPath: new ThemeIcon('rocket'),
986-
onDidSelect: () =>
987-
void executeCommand<OpenWalkthroughCommandArgs>(Commands.OpenWalkthrough, {
988-
step: 'accelerate-pr-reviews',
989-
source: 'launchpad',
990-
detail: 'info',
980+
const hasConnectedIntegration = some(context.connectedIntegrations.values(), c => c);
981+
const confirmations: (QuickPickItemOfT<IntegrationId> | DirectiveQuickPickItem)[] = !hasConnectedIntegration
982+
? [
983+
createDirectiveQuickPickItem(Directive.Cancel, undefined, {
984+
label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked',
985+
detail: 'Click to learn more about Launchpad',
986+
iconPath: new ThemeIcon('rocket'),
987+
onDidSelect: () =>
988+
void executeCommand<OpenWalkthroughCommandArgs>(Commands.OpenWalkthrough, {
989+
step: 'accelerate-pr-reviews',
990+
source: 'launchpad',
991+
detail: 'info',
992+
}),
991993
}),
992-
}),
993-
createQuickPickSeparator(),
994-
];
994+
createQuickPickSeparator(),
995+
]
996+
: [];
995997

996998
for (const integration of supportedLaunchpadIntegrations) {
997999
if (context.connectedIntegrations.get(integration)) {
@@ -1032,19 +1034,12 @@ export class LaunchpadCommand extends QuickCommand<State> {
10321034
{ placeholder: 'Connect an integration to get started with Launchpad', buttons: [], ignoreFocusOut: false },
10331035
);
10341036

1035-
// Note: This is a hack to allow the quickpick to stay alive after the user finishes connecting the integration.
1036-
// Otherwise it disappears.
1037-
let freeze!: () => Disposable;
1038-
step.onDidActivate = qp => {
1039-
freeze = () => freezeStep(step, qp);
1040-
};
1041-
10421037
const selection: StepSelection<typeof step> = yield step;
10431038
if (canPickStepContinue(step, state, selection)) {
1044-
const resume = freeze();
1039+
const resume = step.freeze?.();
10451040
const chosenIntegrationId = selection[0].item;
10461041
const connected = await this.ensureIntegrationConnected(chosenIntegrationId);
1047-
return { connected: connected ? chosenIntegrationId : false, resume: () => resume[Symbol.dispose]() };
1042+
return { connected: connected ? chosenIntegrationId : false, resume: () => resume?.[Symbol.dispose]() };
10481043
}
10491044

10501045
return StepResultBreak;
@@ -1058,18 +1053,22 @@ export class LaunchpadCommand extends QuickCommand<State> {
10581053
const step = this.createConfirmStep(
10591054
`${this.title} \u00a0\u2022\u00a0 Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration`,
10601055
[
1061-
createDirectiveQuickPickItem(Directive.Cancel, undefined, {
1062-
label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked',
1063-
detail: 'Click to learn more about Launchpad',
1064-
iconPath: new ThemeIcon('rocket'),
1065-
onDidSelect: () =>
1066-
void executeCommand<OpenWalkthroughCommandArgs>(Commands.OpenWalkthrough, {
1067-
step: 'accelerate-pr-reviews',
1068-
source: 'launchpad',
1069-
detail: 'info',
1070-
}),
1071-
}),
1072-
createQuickPickSeparator(),
1056+
...(hasConnectedIntegration
1057+
? []
1058+
: [
1059+
createDirectiveQuickPickItem(Directive.Cancel, undefined, {
1060+
label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked',
1061+
detail: 'Click to learn more about Launchpad',
1062+
iconPath: new ThemeIcon('rocket'),
1063+
onDidSelect: () =>
1064+
void executeCommand<OpenWalkthroughCommandArgs>(Commands.OpenWalkthrough, {
1065+
step: 'accelerate-pr-reviews',
1066+
source: 'launchpad',
1067+
detail: 'info',
1068+
}),
1069+
}),
1070+
createQuickPickSeparator(),
1071+
]),
10731072
createQuickPickItemOfT(
10741073
{
10751074
label: `Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration...`,
@@ -1091,30 +1090,25 @@ export class LaunchpadCommand extends QuickCommand<State> {
10911090
},
10921091
);
10931092

1094-
// Note: This is a hack to allow the quickpick to stay alive after the user finishes connecting the integration.
1095-
// Otherwise it disappears.
1096-
let freeze!: () => Disposable;
1097-
let quickpick!: QuickPick<any>;
1098-
step.onDidActivate = qp => {
1099-
quickpick = qp;
1100-
freeze = () => freezeStep(step, qp);
1101-
};
1102-
11031093
const selection: StepSelection<typeof step> = yield step;
11041094

11051095
if (canPickStepContinue(step, state, selection)) {
1106-
const previousPlaceholder = quickpick.placeholder;
1107-
quickpick.placeholder = 'Connecting integrations...';
1108-
quickpick.ignoreFocusOut = true;
1109-
const resume = freeze();
1096+
let previousPlaceholder: string | undefined;
1097+
if (step.quickpick) {
1098+
previousPlaceholder = step.quickpick.placeholder;
1099+
step.quickpick.placeholder = 'Connecting integrations...';
1100+
}
1101+
const resume = step.freeze?.();
11101102
const connected = await this.container.integrations.connectCloudIntegrations(
11111103
{ integrationIds: supportedLaunchpadIntegrations },
11121104
{
11131105
source: 'launchpad',
11141106
},
11151107
);
1116-
quickpick.placeholder = previousPlaceholder;
1117-
return { connected: connected, resume: () => resume[Symbol.dispose]() };
1108+
if (step.quickpick) {
1109+
step.quickpick.placeholder = previousPlaceholder;
1110+
}
1111+
return { connected: connected, resume: () => resume?.[Symbol.dispose]() };
11181112
}
11191113

11201114
return StepResultBreak;

src/plus/launchpad/launchpadProvider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,9 @@ export class LaunchpadProvider implements Disposable {
921921
await Promise.allSettled(
922922
supportedLaunchpadIntegrations.map(async integrationId => {
923923
const integration = await this.container.integrations.get(integrationId);
924-
connected.set(integrationId, integration.maybeConnected ?? (await integration.isConnected()));
924+
const isConnected = integration.maybeConnected ?? (await integration.isConnected());
925+
const hasAccess = isConnected && (await integration.access());
926+
connected.set(integrationId, hasAccess);
925927
}),
926928
);
927929

0 commit comments

Comments
 (0)