Skip to content

Commit 30ca4c8

Browse files
committed
Searches GitHub PR by the entered URL
(#3543, #3684)
1 parent 32d9ebc commit 30ca4c8

File tree

3 files changed

+90
-33
lines changed

3 files changed

+90
-33
lines changed

src/constants.telemetry.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,12 @@ export type TelemetryEvents = {
294294
/** Sent when a launchpad operation is taking longer than a set timeout to complete */
295295
'launchpad/operation/slow': {
296296
timeout: number;
297-
operation: 'getMyPullRequests' | 'getCodeSuggestions' | 'getEnrichedItems' | 'getCodeSuggestionCounts';
297+
operation:
298+
| 'getPullRequest'
299+
| 'getMyPullRequests'
300+
| 'getCodeSuggestions'
301+
| 'getEnrichedItems'
302+
| 'getCodeSuggestionCounts';
298303
duration: number;
299304
};
300305

src/plus/launchpad/launchpad.ts

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export class LaunchpadCommand extends QuickCommand<State> {
419419
i: LaunchpadItem,
420420
ui: LaunchpadGroup,
421421
topItem: LaunchpadItem | undefined,
422+
alwaysShow: boolean | undefined,
422423
): LaunchpadItemQuickPickItem => {
423424
const buttons = [];
424425

@@ -449,6 +450,7 @@ export class LaunchpadCommand extends QuickCommand<State> {
449450
i.actionableCategory === 'other' ? '' : `${actionGroupMap.get(i.actionableCategory)![0]} \u2022 `
450451
}${fromNow(i.updatedDate)} by @${i.author!.username}`,
451452

453+
alwaysShow: alwaysShow,
452454
buttons: buttons,
453455
iconPath: i.author?.avatarUrl != null ? Uri.parse(i.author.avatarUrl) : undefined,
454456
item: i,
@@ -457,7 +459,7 @@ export class LaunchpadCommand extends QuickCommand<State> {
457459
};
458460
};
459461

460-
const getItems = (result: LaunchpadCategorizedResult) => {
462+
const getItems = (result: LaunchpadCategorizedResult, isSearching?: boolean) => {
461463
const items: (LaunchpadItemQuickPickItem | DirectiveQuickPickItem | ConnectMoreIntegrationsItem)[] = [];
462464

463465
if (result.items?.length) {
@@ -472,18 +474,21 @@ export class LaunchpadCommand extends QuickCommand<State> {
472474
for (const [ui, groupItems] of uiGroups) {
473475
if (!groupItems.length) continue;
474476

475-
items.push(...buildGroupHeading(ui, groupItems.length));
476-
477-
if (context.collapsed.get(ui)) continue;
477+
if (!isSearching) {
478+
items.push(...buildGroupHeading(ui, groupItems.length));
479+
if (context.collapsed.get(ui)) {
480+
continue;
481+
}
482+
}
478483

479-
items.push(...groupItems.map(i => buildLaunchpadQuickPickItem(i, ui, topItem)));
484+
items.push(...groupItems.map(i => buildLaunchpadQuickPickItem(i, ui, topItem, isSearching)));
480485
}
481486
}
482487

483488
return items;
484489
};
485490

486-
function getItemsAndPlaceholder() {
491+
function getItemsAndPlaceholder(isSearching?: boolean) {
487492
if (context.result.error != null) {
488493
return {
489494
placeholder: `Unable to load items (${
@@ -506,17 +511,18 @@ export class LaunchpadCommand extends QuickCommand<State> {
506511

507512
return {
508513
placeholder: 'Choose an item to focus on',
509-
items: getItems(context.result),
514+
items: getItems(context.result, isSearching),
510515
};
511516
}
512517

513518
const updateItems = async (
514519
quickpick: QuickPick<LaunchpadItemQuickPickItem | DirectiveQuickPickItem | ConnectMoreIntegrationsItem>,
515520
) => {
521+
const search = quickpick.value;
516522
quickpick.busy = true;
517523

518524
try {
519-
await updateContextItems(this.container, context, { force: true });
525+
await updateContextItems(this.container, context, { force: true, search: search });
520526

521527
const { items, placeholder } = getItemsAndPlaceholder();
522528
quickpick.placeholder = placeholder;
@@ -527,8 +533,7 @@ export class LaunchpadCommand extends QuickCommand<State> {
527533
};
528534

529535
const { items, placeholder } = getItemsAndPlaceholder();
530-
531-
let groupsHidden = false;
536+
const nonGroupedItems = items.filter(i => !isDirectiveQuickPickItem(i));
532537

533538
const step = createPickStep({
534539
title: context.title,
@@ -543,29 +548,27 @@ export class LaunchpadCommand extends QuickCommand<State> {
543548
LaunchpadSettingsQuickInputButton,
544549
RefreshQuickInputButton,
545550
],
546-
onDidChangeValue: quickpick => {
551+
onDidChangeValue: async quickpick => {
547552
const { value } = quickpick;
548-
const hideGroups = Boolean(quickpick.value?.length);
549-
550-
if (groupsHidden !== hideGroups) {
551-
groupsHidden = hideGroups;
552-
quickpick.items = hideGroups ? items.filter(i => !isDirectiveQuickPickItem(i)) : items;
553-
}
554-
const activeLaunchpadItems = quickpick.activeItems.filter(
555-
(i): i is LaunchpadItemQuickPickItem => 'item' in i && !i.alwaysShow,
556-
);
553+
const hideGroups = Boolean(value?.length);
554+
const consideredItems = hideGroups ? nonGroupedItems : items;
557555

558556
let updated = false;
559-
for (const item of quickpick.items) {
557+
for (const item of consideredItems) {
560558
if (item.alwaysShow) {
561559
item.alwaysShow = false;
562560
updated = true;
563561
}
564562
}
565-
if (updated) {
566-
// Force quickpick to update by changing the items object:
567-
quickpick.items = [...quickpick.items];
568-
}
563+
564+
// By doing the following we make sure we operate with the PRs that belong to Launchpad initially.
565+
// Also, when we re-create the array, we make sure that `alwaysShow` updates are applied.
566+
quickpick.items =
567+
updated && quickpick.items === consideredItems ? [...consideredItems] : consideredItems;
568+
569+
const activeLaunchpadItems = quickpick.activeItems.filter(
570+
(i): i is LaunchpadItemQuickPickItem => 'item' in i,
571+
);
569572

570573
if (!value?.length || activeLaunchpadItems.length) {
571574
// Nothing to search
@@ -588,7 +591,12 @@ export class LaunchpadCommand extends QuickCommand<State> {
588591
item.alwaysShow = true;
589592
// Force quickpick to update by changing the items object:
590593
quickpick.items = [...quickpick.items];
594+
// We have found an item that matches to the URL.
595+
// Now it will be displayed as the found item and we exit this function now without sending any requests to API:
596+
return true;
591597
}
598+
// Nothing is found above, so let's perform search in the API:
599+
await updateItems(quickpick);
592600
}
593601
}
594602

@@ -1377,7 +1385,11 @@ function getIntegrationTitle(integrationId: string): string {
13771385
}
13781386
}
13791387

1380-
async function updateContextItems(container: Container, context: Context, options?: { force?: boolean }) {
1388+
async function updateContextItems(
1389+
container: Container,
1390+
context: Context,
1391+
options?: { force?: boolean; search?: string },
1392+
) {
13811393
context.result = await container.launchpad.getCategorizedItems(options);
13821394
if (container.telemetry.enabled) {
13831395
updateTelemetryContext(context);

src/plus/launchpad/launchpadProvider.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { PullRequest, SearchedPullRequest } from '../../git/models/pullRequ
1919
import {
2020
getComparisonRefsForPullRequest,
2121
getOrOpenPullRequestRepository,
22+
getPullRequestIdentityValuesFromSearch,
2223
getRepositoryIdentityForPullRequest,
2324
} from '../../git/models/pullRequest';
2425
import type { GitRemote } from '../../git/models/remote';
@@ -41,6 +42,7 @@ import { showInspectView } from '../../webviews/commitDetails/actions';
4142
import type { ShowWipArgs } from '../../webviews/commitDetails/protocol';
4243
import type { IntegrationResult } from '../integrations/integration';
4344
import type { ConnectionStateChangeEvent } from '../integrations/integrationService';
45+
import type { GitHubRepositoryDescriptor } from '../integrations/providers/github';
4446
import type { EnrichablePullRequest, ProviderActionablePullRequest } from '../integrations/providers/models';
4547
import {
4648
fromProviderPullRequest,
@@ -318,6 +320,34 @@ export class LaunchpadProvider implements Disposable {
318320
return { prs: prs, suggestionCounts: suggestionCounts };
319321
}
320322

323+
private async getSearchedPullRequests(search: string) {
324+
const { ownerAndRepo, prNumber } = getPullRequestIdentityValuesFromSearch(search);
325+
let result: TimedResult<SearchedPullRequest[] | undefined> | undefined;
326+
327+
if (prNumber != null) {
328+
if (ownerAndRepo != null) {
329+
// TODO: This needs to be generalized to work outside of GitHub
330+
const integration = await this.container.integrations.get(HostingIntegrationId.GitHub);
331+
const [owner, repo] = ownerAndRepo.split('/', 2);
332+
const descriptor: GitHubRepositoryDescriptor = {
333+
key: ownerAndRepo,
334+
owner: owner,
335+
name: repo,
336+
};
337+
const pr = await withDurationAndSlowEventOnTimeout(
338+
integration?.getPullRequest(descriptor, prNumber),
339+
'getPullRequest',
340+
this.container,
341+
);
342+
if (pr?.value != null) {
343+
result = { value: [{ pullRequest: pr.value, reasons: [] }], duration: pr.duration };
344+
return { prs: result, suggestionCounts: undefined };
345+
}
346+
}
347+
}
348+
return { prs: undefined, suggestionCounts: undefined };
349+
}
350+
321351
private _enrichedItems: CachedLaunchpadPromise<TimedResult<EnrichedItem[]>> | undefined;
322352
@debug<LaunchpadProvider['getEnrichedItems']>({ args: { 0: o => `force=${o?.force}` } })
323353
private async getEnrichedItems(options?: { cancellation?: CancellationToken; force?: boolean }) {
@@ -612,12 +642,12 @@ export class LaunchpadProvider implements Disposable {
612642
@gate<LaunchpadProvider['getCategorizedItems']>(o => `${o?.force ?? false}`)
613643
@log<LaunchpadProvider['getCategorizedItems']>({ args: { 0: o => `force=${o?.force}`, 1: false } })
614644
async getCategorizedItems(
615-
options?: { force?: boolean },
645+
options?: { force?: boolean; search?: string },
616646
cancellation?: CancellationToken,
617647
): Promise<LaunchpadCategorizedResult> {
618648
const scope = getLogScope();
619649

620-
const fireRefresh = options?.force || this._prs == null;
650+
const fireRefresh = !options?.search && (options?.force || this._prs == null);
621651

622652
const ignoredRepositories = new Set(
623653
(configuration.get('launchpad.ignoredRepositories') ?? []).map(r => r.toLowerCase()),
@@ -638,7 +668,9 @@ export class LaunchpadProvider implements Disposable {
638668
const [_, enrichedItemsResult, prsWithCountsResult] = await Promise.allSettled([
639669
this.container.git.isDiscoveringRepositories,
640670
this.getEnrichedItems({ force: options?.force, cancellation: cancellation }),
641-
this.getPullRequestsWithSuggestionCounts({ force: options?.force, cancellation: cancellation }),
671+
options?.search
672+
? this.getSearchedPullRequests(options.search)
673+
: this.getPullRequestsWithSuggestionCounts({ force: options?.force, cancellation: cancellation }),
642674
]);
643675

644676
if (cancellation?.isCancellationRequested) throw new CancellationError();
@@ -752,7 +784,7 @@ export class LaunchpadProvider implements Disposable {
752784
item.suggestedActionCategory,
753785
)!;
754786
// category overrides
755-
if (staleDate != null && item.updatedDate.getTime() < staleDate.getTime()) {
787+
if (!options?.search && staleDate != null && item.updatedDate.getTime() < staleDate.getTime()) {
756788
actionableCategory = 'other';
757789
} else if (codeSuggestionsCount > 0 && item.viewer.isAuthor) {
758790
actionableCategory = 'code-suggestions';
@@ -788,7 +820,10 @@ export class LaunchpadProvider implements Disposable {
788820
};
789821
return result;
790822
} finally {
791-
this.updateGroupedIds(result?.items ?? []);
823+
if (!options?.search) {
824+
this.updateGroupedIds(result?.items ?? []);
825+
}
826+
792827
if (result != null && fireRefresh) {
793828
this._onDidRefresh.fire(result);
794829
}
@@ -1045,7 +1080,12 @@ const slowEventTimeout = 1000 * 30; // 30 seconds
10451080

10461081
function withDurationAndSlowEventOnTimeout<T>(
10471082
promise: Promise<T>,
1048-
name: 'getMyPullRequests' | 'getCodeSuggestionCounts' | 'getCodeSuggestions' | 'getEnrichedItems',
1083+
name:
1084+
| 'getPullRequest'
1085+
| 'getMyPullRequests'
1086+
| 'getCodeSuggestionCounts'
1087+
| 'getCodeSuggestions'
1088+
| 'getEnrichedItems',
10491089
container: Container,
10501090
): Promise<TimedResult<T>> {
10511091
return timedWithSlowThreshold(promise, {

0 commit comments

Comments
 (0)