Skip to content

Commit e11995a

Browse files
committed
Connects integrations if they are not connected
(#3621, #3698)
1 parent 2e05079 commit e11995a

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

docs/telemetry-events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,7 @@ void
13311331
'repository.visibility': 'private' | 'public' | 'local',
13321332
'repoPrivacy': 'private' | 'public' | 'local',
13331333
'filesChanged': number,
1334-
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | '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'
1334+
'source': 'graph' | 'patchDetails' | 'settings' | 'timeline' | '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'
13351335
}
13361336
```
13371337

src/constants.telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ export type Sources =
620620
| 'quick-wizard'
621621
| 'remoteProvider'
622622
| 'settings'
623+
| 'startWork'
623624
| 'timeline'
624625
| 'trial-indicator'
625626
| 'scm-input'

src/plus/startWork/startWork.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import type { QuickPick } from 'vscode';
12
import { Uri } from 'vscode';
23
import type {
4+
AsyncStepResultGenerator,
35
PartialStepState,
46
StepGenerator,
57
StepResultGenerator,
@@ -10,16 +12,20 @@ import {
1012
canPickStepContinue,
1113
createPickStep,
1214
endSteps,
15+
freezeStep,
1316
QuickCommand,
1417
StepResultBreak,
1518
} from '../../commands/quickCommand';
1619
import { proBadge } from '../../constants';
20+
import type { IntegrationId } from '../../constants.integrations';
1721
import { HostingIntegrationId } from '../../constants.integrations';
1822
import type { Container } from '../../container';
1923
import type { SearchedIssue } from '../../git/models/issue';
2024
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
25+
import { createQuickPickItemOfT } from '../../quickpicks/items/common';
2126
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
2227
import { fromNow } from '../../system/date';
28+
import { some } from '../../system/iterable';
2329

2430
export type StartWorkItem = {
2531
item: SearchedIssue;
@@ -30,6 +36,7 @@ export type StartWorkResult = { items: StartWorkItem[] };
3036
interface Context {
3137
result: StartWorkResult;
3238
title: string;
39+
connectedIntegrations: Map<IntegrationId, boolean>;
3340
}
3441

3542
interface State {
@@ -52,6 +59,8 @@ function assertsStartWorkStepState(state: StepState<State>): asserts state is St
5259
throw new Error('Missing item');
5360
}
5461

62+
export const supportedStartWorkIntegrations = [HostingIntegrationId.GitHub];
63+
5564
export class StartWorkCommand extends QuickCommand<State> {
5665
constructor(container: Container) {
5766
super(container, 'startWork', 'startWork', `Start Work\u00a0\u00a0${proBadge}`, {
@@ -71,11 +80,20 @@ export class StartWorkCommand extends QuickCommand<State> {
7180
const context: Context = {
7281
result: { items: [] },
7382
title: this.title,
83+
connectedIntegrations: await this.getConnectedIntegrations(),
7484
};
7585

7686
while (this.canStepsContinue(state)) {
7787
context.title = this.title;
7888

89+
const hasConnectedIntegrations = [...context.connectedIntegrations.values()].some(c => c);
90+
if (!hasConnectedIntegrations) {
91+
const result = yield* this.confirmCloudIntegrationsConnectStep(state, context);
92+
if (result === StepResultBreak) {
93+
return result;
94+
}
95+
}
96+
7997
await updateContextItems(this.container, context);
8098

8199
if (state.counter < 1 || state.item == null) {
@@ -101,6 +119,65 @@ export class StartWorkCommand extends QuickCommand<State> {
101119
return state.counter < 0 ? StepResultBreak : undefined;
102120
}
103121

122+
private async *confirmCloudIntegrationsConnectStep(
123+
state: StepState<State>,
124+
context: Context,
125+
): AsyncStepResultGenerator<{ connected: boolean | IntegrationId; resume: () => void }> {
126+
// 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?
127+
const hasConnectedIntegration = some(context.connectedIntegrations.values(), c => c);
128+
const step = this.createConfirmStep(
129+
`${this.title} \u00a0\u2022\u00a0 Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration`,
130+
[
131+
createQuickPickItemOfT(
132+
{
133+
label: `Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration...`,
134+
detail: hasConnectedIntegration
135+
? 'Connect additional integrations to view their issues in Start Work'
136+
: 'Connect an integration to accelerate your work',
137+
picked: true,
138+
},
139+
true,
140+
),
141+
],
142+
createDirectiveQuickPickItem(Directive.Cancel, false, { label: 'Cancel' }),
143+
{
144+
placeholder: hasConnectedIntegration
145+
? 'Connect additional integrations to Start Work'
146+
: 'Connect an integration to get started with Start Work',
147+
buttons: [],
148+
ignoreFocusOut: true,
149+
},
150+
);
151+
152+
// Note: This is a hack to allow the quickpick to stay alive after the user finishes connecting the integration.
153+
// Otherwise it disappears.
154+
let freeze!: () => Disposable;
155+
let quickpick!: QuickPick<any>;
156+
step.onDidActivate = qp => {
157+
quickpick = qp;
158+
freeze = () => freezeStep(step, qp);
159+
};
160+
161+
const selection: StepSelection<typeof step> = yield step;
162+
163+
if (canPickStepContinue(step, state, selection)) {
164+
const previousPlaceholder = quickpick.placeholder;
165+
quickpick.placeholder = 'Connecting integrations...';
166+
quickpick.ignoreFocusOut = true;
167+
const resume = freeze();
168+
const connected = await this.container.integrations.connectCloudIntegrations(
169+
{ integrationIds: supportedStartWorkIntegrations },
170+
{
171+
source: 'startWork',
172+
},
173+
);
174+
quickpick.placeholder = previousPlaceholder;
175+
return { connected: connected, resume: () => resume[Symbol.dispose]() };
176+
}
177+
178+
return StepResultBreak;
179+
}
180+
104181
private *pickIssueStep(state: StepState<State>, context: Context): StepResultGenerator<StartWorkItem> {
105182
const buildIssueItem = (i: StartWorkItem) => {
106183
return {
@@ -158,6 +235,18 @@ export class StartWorkCommand extends QuickCommand<State> {
158235
const element = selection[0];
159236
return { ...element.item };
160237
}
238+
239+
private async getConnectedIntegrations(): Promise<Map<IntegrationId, boolean>> {
240+
const connected = new Map<IntegrationId, boolean>();
241+
await Promise.allSettled(
242+
supportedStartWorkIntegrations.map(async integrationId => {
243+
const integration = await this.container.integrations.get(integrationId);
244+
connected.set(integrationId, integration.maybeConnected ?? (await integration.isConnected()));
245+
}),
246+
);
247+
248+
return connected;
249+
}
161250
}
162251

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

0 commit comments

Comments
 (0)