Skip to content

Commit 00998ee

Browse files
committed
Connects integrations if they are not connected
(#3621)
1 parent afc8388 commit 00998ee

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

docs/telemetry-events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1320,7 +1320,7 @@ void
13201320
'repository.visibility': 'private' | 'public' | 'local',
13211321
'repoPrivacy': 'private' | 'public' | 'local',
13221322
'filesChanged': number,
1323-
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'welcome' | 'home' | 'code-suggest' | 'account' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'notification' | 'prompt' | 'quick-wizard' | 'remoteProvider' | 'trial-indicator' | 'scm-input' | 'subscription' | 'walkthrough' | 'worktrees'
1323+
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'welcome' | 'home' | 'code-suggest' | 'account' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'notification' | 'prompt' | 'quick-wizard' | 'remoteProvider' | 'startWork' | 'trial-indicator' | 'scm-input' | 'subscription' | 'walkthrough' | 'worktrees'
13241324
}
13251325
```
13261326

src/constants.telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ export type Sources =
606606
| 'quick-wizard'
607607
| 'remoteProvider'
608608
| 'settings'
609+
| 'startWork'
609610
| 'timeline'
610611
| 'trial-indicator'
611612
| 'scm-input'

src/plus/startWork/startWork.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { QuickInputButton } from 'vscode';
1+
import type { QuickInputButton, QuickPick } from 'vscode';
22
import { ThemeIcon, Uri } from 'vscode';
33
import type {
4+
AsyncStepResultGenerator,
45
PartialStepState,
56
StepGenerator,
67
StepResultGenerator,
@@ -11,10 +12,12 @@ import {
1112
canPickStepContinue,
1213
createPickStep,
1314
endSteps,
15+
freezeStep,
1416
QuickCommand,
1517
StepResultBreak,
1618
} from '../../commands/quickCommand';
1719
import { proBadge } from '../../constants';
20+
import type { IntegrationId } from '../../constants.integrations';
1821
import { HostingIntegrationId } from '../../constants.integrations';
1922
import type { Container } from '../../container';
2023
import type { SearchedIssue } from '../../git/models/issue';
@@ -23,6 +26,7 @@ import { createQuickPickItemOfT, createQuickPickSeparator } from '../../quickpic
2326
import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive';
2427
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
2528
import { fromNow } from '../../system/date';
29+
import { some } from '../../system/iterable';
2630

2731
export type StartWorkItem = {
2832
item: SearchedIssue;
@@ -38,6 +42,7 @@ export type StartWorkResult = { items: StartWorkItem[] };
3842
interface Context {
3943
result: StartWorkResult;
4044
title: string;
45+
connectedIntegrations: Map<IntegrationId, boolean>;
4146
}
4247

4348
interface State {
@@ -60,6 +65,8 @@ function assertsStartWorkStepState(state: StepState<State>): asserts state is St
6065
throw new Error('Missing item');
6166
}
6267

68+
export const supportedStartWorkIntegrations = [HostingIntegrationId.GitHub];
69+
6370
export class StartWorkCommand extends QuickCommand<State> {
6471
constructor(container: Container) {
6572
super(container, 'startWork', 'startWork', `Start Work\u00a0\u00a0${proBadge}`, {
@@ -79,11 +86,20 @@ export class StartWorkCommand extends QuickCommand<State> {
7986
const context: Context = {
8087
result: { items: [] },
8188
title: this.title,
89+
connectedIntegrations: await this.getConnectedIntegrations(),
8290
};
8391

8492
while (this.canStepsContinue(state)) {
8593
context.title = this.title;
8694

95+
const hasConnectedIntegrations = [...context.connectedIntegrations.values()].some(c => c);
96+
if (!hasConnectedIntegrations) {
97+
const result = yield* this.confirmCloudIntegrationsConnectStep(state, context);
98+
if (result === StepResultBreak) {
99+
return result;
100+
}
101+
}
102+
87103
await updateContextItems(this.container, context);
88104

89105
if (state.counter < 1 || state.item == null) {
@@ -115,6 +131,65 @@ export class StartWorkCommand extends QuickCommand<State> {
115131
return state.counter < 0 ? StepResultBreak : undefined;
116132
}
117133

134+
private async *confirmCloudIntegrationsConnectStep(
135+
state: StepState<State>,
136+
context: Context,
137+
): AsyncStepResultGenerator<{ connected: boolean | IntegrationId; resume: () => void }> {
138+
// TODO: This step is almost an exact copy of the similar one from launchpad.ts. Do we want to do anything about it? Maybe to move it to an util function with ability to parameterize labels?
139+
const hasConnectedIntegration = some(context.connectedIntegrations.values(), c => c);
140+
const step = this.createConfirmStep(
141+
`${this.title} \u00a0\u2022\u00a0 Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration`,
142+
[
143+
createQuickPickItemOfT(
144+
{
145+
label: `Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration...`,
146+
detail: hasConnectedIntegration
147+
? 'Connect additional integrations to view their issues in Start Work'
148+
: 'Connect an integration to accelerate your work',
149+
picked: true,
150+
},
151+
true,
152+
),
153+
],
154+
createDirectiveQuickPickItem(Directive.Cancel, false, { label: 'Cancel' }),
155+
{
156+
placeholder: hasConnectedIntegration
157+
? 'Connect additional integrations to Start Work'
158+
: 'Connect an integration to get started with Start Work',
159+
buttons: [],
160+
ignoreFocusOut: true,
161+
},
162+
);
163+
164+
// Note: This is a hack to allow the quickpick to stay alive after the user finishes connecting the integration.
165+
// Otherwise it disappears.
166+
let freeze!: () => Disposable;
167+
let quickpick!: QuickPick<any>;
168+
step.onDidActivate = qp => {
169+
quickpick = qp;
170+
freeze = () => freezeStep(step, qp);
171+
};
172+
173+
const selection: StepSelection<typeof step> = yield step;
174+
175+
if (canPickStepContinue(step, state, selection)) {
176+
const previousPlaceholder = quickpick.placeholder;
177+
quickpick.placeholder = 'Connecting integrations...';
178+
quickpick.ignoreFocusOut = true;
179+
const resume = freeze();
180+
const connected = await this.container.integrations.connectCloudIntegrations(
181+
{ integrationIds: supportedStartWorkIntegrations },
182+
{
183+
source: 'startWork',
184+
},
185+
);
186+
quickpick.placeholder = previousPlaceholder;
187+
return { connected: connected, resume: () => resume[Symbol.dispose]() };
188+
}
189+
190+
return StepResultBreak;
191+
}
192+
118193
private *pickIssueStep(state: StepState<State>, context: Context): StepResultGenerator<StartWorkItem> {
119194
const buildIssueItem = (i: StartWorkItem) => {
120195
const buttons = [StartWorkQuickInputButton];
@@ -229,6 +304,18 @@ export class StartWorkCommand extends QuickCommand<State> {
229304
const selection: StepSelection<typeof step> = yield step;
230305
return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak;
231306
}
307+
308+
private async getConnectedIntegrations(): Promise<Map<IntegrationId, boolean>> {
309+
const connected = new Map<IntegrationId, boolean>();
310+
await Promise.allSettled(
311+
supportedStartWorkIntegrations.map(async integrationId => {
312+
const integration = await this.container.integrations.get(integrationId);
313+
connected.set(integrationId, integration.maybeConnected ?? (await integration.isConnected()));
314+
}),
315+
);
316+
317+
return connected;
318+
}
232319
}
233320

234321
async function updateContextItems(container: Container, context: Context) {

0 commit comments

Comments
 (0)