Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions src/plus/integrations/integrationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,29 +792,29 @@ export class IntegrationService implements Disposable {
}

const results = await Promise.allSettled(promises);

const successfulResults = [
...flatten(
filterMap(results, r =>
r.status === 'fulfilled' && r.value?.value != null ? r.value.value : undefined,
),
),
];
const errors = [
...filterMap(results, r =>
r.status === 'fulfilled' && r.value?.error != null ? r.value.error : undefined,
),
];
if (errors.length) {
return {
error: errors.length === 1 ? errors[0] : new AggregateError(errors),
duration: Date.now() - start,
};
}

const error =
errors.length === 0
? undefined
: errors.length === 1
? errors[0]
: new AggregateError(errors, 'Failed to get some pull requests');

return {
value: [
...flatten(
filterMap(results, r =>
r.status === 'fulfilled' && r.value != null && r.value?.error == null
? r.value.value
: undefined,
),
),
],
value: successfulResults,
error: error,
duration: Date.now() - start,
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/plus/integrations/models/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extend
export type IntegrationConnectedKey<T extends IntegrationIds = IntegrationIds> = `connected:${IntegrationKey<T>}`;

export type IntegrationResult<T> =
| { value: T; duration?: number; error?: never }
| { value: T; duration?: number; error?: Error }
| { error: Error; duration?: number; value?: never }
| undefined;

Expand Down
66 changes: 57 additions & 9 deletions src/plus/launchpad/launchpad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { IntegrationIds } from '../../constants.integrations';
import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../../constants.integrations';
import type { LaunchpadTelemetryContext, Source, Sources, TelemetryEvents } from '../../constants.telemetry';
import type { Container } from '../../container';
import { AuthenticationError } from '../../errors';
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
import { createQuickPickItemOfT, createQuickPickSeparator } from '../../quickpicks/items/common';
import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive';
Expand All @@ -52,6 +53,8 @@ import { openUrl } from '../../system/-webview/vscode/uris';
import { getScopedCounter } from '../../system/counter';
import { fromNow } from '../../system/date';
import { some } from '../../system/iterable';
import { Logger } from '../../system/logger';
import { AggregateError } from '../../system/promise';
import { interpolate, pluralize } from '../../system/string';
import { ProviderBuildStatusState, ProviderPullRequestReviewState } from '../integrations/providers/models';
import type { LaunchpadCategorizedResult, LaunchpadItem } from './launchpadProvider';
Expand Down Expand Up @@ -157,6 +160,11 @@ const instanceCounter = getScopedCounter();

const defaultCollapsedGroups: LaunchpadGroup[] = ['draft', 'other', 'snoozed'];

const OpenLogsQuickInputButton: QuickInputButton = {
iconPath: new ThemeIcon('output'),
tooltip: 'Open Logs',
};

export class LaunchpadCommand extends QuickCommand<State> {
private readonly source: Source;
private readonly telemetryContext: LaunchpadTelemetryContext | undefined;
Expand Down Expand Up @@ -565,10 +573,10 @@ export class LaunchpadCommand extends QuickCommand<State> {
return groupedAndSorted;
};

function getItemsAndQuickpickProps(isFiltering?: boolean) {
const getItemsAndQuickpickProps = (isFiltering?: boolean) => {
const result = context.inSearch ? context.searchResult : context.result;

if (result?.error != null) {
if (result?.error != null && !result?.items?.length) {
return {
title: `${context.title} \u00a0\u2022\u00a0 Unable to Load Items`,
placeholder: `Unable to load items (${
Expand All @@ -582,7 +590,7 @@ export class LaunchpadCommand extends QuickCommand<State> {
};
}

if (!result?.items.length) {
if (!result?.items?.length) {
if (context.inSearch === 'mode') {
return {
title: `Search For Pull Request \u00a0\u2022\u00a0 ${context.title}`,
Expand Down Expand Up @@ -616,6 +624,11 @@ export class LaunchpadCommand extends QuickCommand<State> {
}

const items = getLaunchpadQuickPickItems(result.items, isFiltering);

// Add error information item if there's an error but items were still loaded
const errorItem: DirectiveQuickPickItem | undefined =
result?.error != null ? this.createErrorQuickPickItem(result.error) : undefined;

const hasPicked = items.some(i => i.picked);
if (context.inSearch === 'mode') {
const offItem: ToggleSearchModeQuickPickItem = {
Expand All @@ -630,7 +643,9 @@ export class LaunchpadCommand extends QuickCommand<State> {
return {
title: `Search For Pull Request \u00a0\u2022\u00a0 ${context.title}`,
placeholder: 'Enter a term to search for a pull request to act on',
items: isFiltering ? [...items, offItem] : [offItem, ...items],
items: isFiltering
? [...(errorItem != null ? [errorItem] : []), ...items, offItem]
: [offItem, ...(errorItem != null ? [errorItem] : []), ...items],
};
}

Expand All @@ -646,10 +661,14 @@ export class LaunchpadCommand extends QuickCommand<State> {
title: context.title,
placeholder: 'Choose a pull request or paste a pull request URL to act on',
items: isFiltering
? [...items, onItem]
: [onItem, ...getLaunchpadQuickPickItems(result.items, isFiltering)],
? [...(errorItem != null ? [errorItem] : []), ...items, onItem]
: [
onItem,
...(errorItem != null ? [errorItem] : []),
...getLaunchpadQuickPickItems(result.items, isFiltering),
],
};
}
};

const updateItems = async (
quickpick: QuickPick<
Expand Down Expand Up @@ -830,6 +849,16 @@ export class LaunchpadCommand extends QuickCommand<State> {
return;
}

if (button === OpenLogsQuickInputButton) {
Logger.showOutputChannel();
return;
}

if (button === ConnectIntegrationButton) {
await this.container.integrations.manageCloudIntegrations({ source: 'launchpad' });
return;
}

if (!item) return;

switch (button) {
Expand Down Expand Up @@ -1403,6 +1432,25 @@ export class LaunchpadCommand extends QuickCommand<State> {
this.source,
);
}

private createErrorQuickPickItem(error: Error): DirectiveQuickPickItem {
if (error instanceof AggregateError) {
const firstAuthError = error.errors.find(e => e instanceof AuthenticationError);
error = firstAuthError ?? error.errors[0] ?? error;
}

const isAuthError = error instanceof AuthenticationError;

return createDirectiveQuickPickItem(Directive.Noop, false, {
label: isAuthError ? '$(warning) Authentication Required' : '$(warning) Unable to fully load items',
detail: isAuthError
? `${String(error)} — Click to reconnect your integration`
: error.name === 'HttpError' && 'status' in error && typeof error.status === 'number'
? `${error.status}: ${String(error)}`
: String(error),
buttons: isAuthError ? [ConnectIntegrationButton, OpenLogsQuickInputButton] : [OpenLogsQuickInputButton],
});
}
}

function getLaunchpadItemInformationRows(
Expand Down Expand Up @@ -1657,10 +1705,10 @@ function updateTelemetryContext(context: Context) {
if (context.telemetryContext == null) return;

let updatedContext: NonNullable<(typeof context)['telemetryContext']>;
if (context.result.error != null) {
if (context.result.error != null || !context.result.items) {
updatedContext = {
...context.telemetryContext,
'items.error': String(context.result.error),
'items.error': String(context.result.error ?? 'items not loaded'),
};
} else {
const grouped = countLaunchpadItemGroups(context.result.items);
Expand Down
13 changes: 5 additions & 8 deletions src/plus/launchpad/launchpadProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export type LaunchpadCategorizedResult =
| {
items: LaunchpadItem[];
timings?: LaunchpadCategorizedTimings;
error?: never;
error?: Error;
}
| {
error: Error;
Expand Down Expand Up @@ -221,11 +221,6 @@ export class LaunchpadProvider implements Disposable {
}

const prs = getSettledValue(prsResult)?.value;
if (prs?.error != null) {
Logger.error(prs.error, scope, 'Failed to get pull requests');
throw prs.error;
}

const subscription = getSettledValue(subscriptionResult);

let suggestionCounts;
Expand All @@ -252,7 +247,7 @@ export class LaunchpadProvider implements Disposable {
search,
connectedIntegrations,
);
const result: { readonly value: PullRequest[]; duration: number } = {
const result: { readonly value: PullRequest[]; duration: number; error?: Error } = {
value: [],
duration: 0,
};
Expand Down Expand Up @@ -690,7 +685,7 @@ export class LaunchpadProvider implements Disposable {
isSearching
? typeof options.search === 'string'
? this.getSearchedPullRequests(options.search, cancellation)
: { prs: { value: options.search, duration: 0 }, suggestionCounts: undefined }
: { prs: { value: options.search, duration: 0, error: undefined }, suggestionCounts: undefined }
: this.getPullRequestsWithSuggestionCounts({ force: options?.force, cancellation: cancellation }),
]);

Expand Down Expand Up @@ -719,6 +714,7 @@ export class LaunchpadProvider implements Disposable {
codeSuggestionCounts: prsWithSuggestionCounts?.suggestionCounts?.duration,
enrichedItems: enrichedItems?.duration,
},
error: prsWithSuggestionCounts?.prs?.error,
};
return result;
}
Expand Down Expand Up @@ -848,6 +844,7 @@ export class LaunchpadProvider implements Disposable {
codeSuggestionCounts: prsWithSuggestionCounts?.suggestionCounts?.duration,
enrichedItems: enrichedItems?.duration,
},
error: prsWithSuggestionCounts?.prs?.error,
};
return result;
} finally {
Expand Down
4 changes: 2 additions & 2 deletions src/webviews/home/homeWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1927,13 +1927,13 @@ async function getLaunchpadItemInfo(
): Promise<LaunchpadItemInfo> {
launchpadPromise ??= container.launchpad.getCategorizedItems();
let result = await launchpadPromise;
if (result.error != null) return undefined;
if (result.error != null || !result.items) return undefined;

let lpi = result.items.find(i => i.url === pr.url);
if (lpi == null) {
// result = await container.launchpad.getCategorizedItems({ search: pr.url });
result = await container.launchpad.getCategorizedItems({ search: [pr] });
if (result.error != null) return undefined;
if (result.error != null || !result.items) return undefined;

lpi = result.items.find(i => i.url === pr.url);
}
Expand Down
Loading