Skip to content

Commit b2c3a99

Browse files
committed
Add output channel command and error actions to Launchpad
(#4492, #4748) Introduces a "Show Output Channel" command accessible via command palette and context menus, with appropriate icons and menu placements. Enhances the Launchpad view to display actionable error messages, distinguishing authentication errors from general failures, and adds context-specific actions for resolving them. Improves user experience by making error states more visible and easier to address directly from the UI.
1 parent 75d7275 commit b2c3a99

File tree

7 files changed

+110
-7
lines changed

7 files changed

+110
-7
lines changed

contributions.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4239,7 +4239,17 @@
42394239
},
42404240
"gitlens.plus.cloudIntegrations.manage": {
42414241
"label": "Manage Integrations...",
4242-
"commandPalette": "gitlens:plus"
4242+
"icon": "$(plug)",
4243+
"commandPalette": "gitlens:plus",
4244+
"menus": {
4245+
"view/item/context": [
4246+
{
4247+
"when": "viewItem =~ /gitlens:launchpad:error\\+auth/",
4248+
"group": "inline",
4249+
"order": 1
4250+
}
4251+
]
4252+
}
42434253
},
42444254
"gitlens.plus.hide": {
42454255
"label": "Hide Pro Features",
@@ -4990,6 +5000,20 @@
49905000
]
49915001
}
49925002
},
5003+
"gitlens.showOutputChannel": {
5004+
"label": "Show Output Channel",
5005+
"icon": "$(output)",
5006+
"commandPalette": "gitlens:enabled",
5007+
"menus": {
5008+
"view/item/context": [
5009+
{
5010+
"when": "viewItem =~ /gitlens:launchpad:error/",
5011+
"group": "inline",
5012+
"order": 2
5013+
}
5014+
]
5015+
}
5016+
},
49935017
"gitlens.showPatchDetailsPage": {
49945018
"label": "Show Patch Details",
49955019
"commandPalette": "gitlens:enabled && gitlens:gk:organization:drafts:enabled && config.gitlens.cloudPatches.enabled"

package.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7879,7 +7879,8 @@
78797879
{
78807880
"command": "gitlens.plus.cloudIntegrations.manage",
78817881
"title": "Manage Integrations...",
7882-
"category": "GitLens"
7882+
"category": "GitLens",
7883+
"icon": "$(plug)"
78837884
},
78847885
{
78857886
"command": "gitlens.plus.hide",
@@ -8162,6 +8163,12 @@
81628163
"title": "Show Line History View",
81638164
"category": "GitLens"
81648165
},
8166+
{
8167+
"command": "gitlens.showOutputChannel",
8168+
"title": "Show Output Channel",
8169+
"category": "GitLens",
8170+
"icon": "$(output)"
8171+
},
81658172
{
81668173
"command": "gitlens.showPatchDetailsPage",
81678174
"title": "Show Patch Details",
@@ -13022,6 +13029,10 @@
1302213029
"command": "gitlens.showLineHistoryView",
1302313030
"when": "gitlens:enabled && !gitlens:hasVirtualFolders"
1302413031
},
13032+
{
13033+
"command": "gitlens.showOutputChannel",
13034+
"when": "gitlens:enabled"
13035+
},
1302513036
{
1302613037
"command": "gitlens.showPatchDetailsPage",
1302713038
"when": "gitlens:enabled && gitlens:gk:organization:drafts:enabled && config.gitlens.cloudPatches.enabled"
@@ -19564,6 +19575,16 @@
1956419575
"group": "5_gitlens_open@2",
1956519576
"alt": "gitlens.copyRemoteFileUrlWithoutRange"
1956619577
},
19578+
{
19579+
"command": "gitlens.showOutputChannel",
19580+
"when": "viewItem =~ /gitlens:launchpad:error/",
19581+
"group": "inline@2"
19582+
},
19583+
{
19584+
"command": "gitlens.plus.cloudIntegrations.manage",
19585+
"when": "viewItem =~ /gitlens:launchpad:error\\+auth/",
19586+
"group": "inline@1"
19587+
},
1956719588
{
1956819589
"command": "gitlens.views.openPullRequestChanges",
1956919590
"when": "viewItem =~ /gitlens:(pullrequest\\b(?=.*?\\b\\+refs\\b)|launchpad:item\\b(?=.*?\\b\\+pr\\b))/",

src/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import './commands/ghpr/openOrCreateWorktree';
3434
import './commands/gitWizard';
3535
import './commands/inviteToLiveShare';
3636
import './commands/inspect';
37+
import './commands/showOutputChannel';
3738
import './commands/logging';
3839
import './commands/openAssociatedPullRequestOnRemote';
3940
import './commands/openBranchesOnRemote';

src/commands/showOutputChannel.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { command } from '../system/-webview/command';
2+
import { Logger } from '../system/logger';
3+
import { GlCommandBase } from './commandBase';
4+
5+
@command()
6+
export class ShowOutputChannelCommand extends GlCommandBase {
7+
constructor() {
8+
super('gitlens.showOutputChannel');
9+
}
10+
11+
execute(): void {
12+
Logger.showOutputChannel();
13+
}
14+
}

src/constants.commands.generated.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export type ContributedCommands =
246246
| 'gitlens.openRevisionFile:editor/title'
247247
| 'gitlens.openWorkingFile:editor/context'
248248
| 'gitlens.openWorkingFile:editor/title'
249+
| 'gitlens.plus.cloudIntegrations.manage'
249250
| 'gitlens.plus.login'
250251
| 'gitlens.pullRepositories'
251252
| 'gitlens.pushRepositories'
@@ -274,6 +275,7 @@ export type ContributedCommands =
274275
| 'gitlens.showInDetailsView'
275276
| 'gitlens.showLineCommitInView'
276277
| 'gitlens.showLineHistoryView'
278+
| 'gitlens.showOutputChannel'
277279
| 'gitlens.showQuickCommitFileDetails'
278280
| 'gitlens.showQuickRevisionDetails:editor'
279281
| 'gitlens.showQuickRevisionDetails:editor/title'
@@ -981,6 +983,7 @@ export type ContributedPaletteCommands =
981983
| 'gitlens.showLaunchpadView'
982984
| 'gitlens.showLineCommitInView'
983985
| 'gitlens.showLineHistoryView'
986+
| 'gitlens.showOutputChannel'
984987
| 'gitlens.showPatchDetailsPage'
985988
| 'gitlens.showQuickBranchHistory'
986989
| 'gitlens.showQuickCommitDetails'

src/views/launchpadView.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { ConfigurationChangeEvent, TreeViewVisibilityChangeEvent } from 'vscode';
2-
import { Disposable, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, window } from 'vscode';
2+
import { Disposable, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, window } from 'vscode';
33
import type { OpenWalkthroughCommandArgs } from '../commands/walkthroughs';
44
import type { LaunchpadViewConfig, ViewFilesLayout } from '../config';
55
import { proBadge } from '../constants';
66
import type { Container } from '../container';
7-
import { AuthenticationRequiredError } from '../errors';
7+
import { AuthenticationError, AuthenticationRequiredError } from '../errors';
88
import { GitUri, unknownGitUri } from '../git/gitUri';
99
import type { PullRequest } from '../git/models/pullRequest';
1010
import type { SubscriptionChangeEvent } from '../plus/gk/subscriptionService';
@@ -16,9 +16,11 @@ import type { LaunchpadGroup } from '../plus/launchpad/models/launchpad';
1616
import { launchpadGroupIconMap, launchpadGroupLabelMap } from '../plus/launchpad/models/launchpad';
1717
import { createCommand, executeCommand } from '../system/-webview/command';
1818
import { configuration } from '../system/-webview/configuration';
19+
import { AggregateError } from '../system/promise';
1920
import { CacheableChildrenViewNode } from './nodes/abstract/cacheableChildrenViewNode';
2021
import type { ClipboardType, ViewNode } from './nodes/abstract/viewNode';
2122
import { ContextValues, getViewNodeId } from './nodes/abstract/viewNode';
23+
import { MessageNode } from './nodes/common';
2224
import type { GroupingNode } from './nodes/groupingNode';
2325
import { LaunchpadViewGroupingNode } from './nodes/launchpadViewGroupingNode';
2426
import { getPullRequestChildren, getPullRequestTooltip } from './nodes/pullRequestNode';
@@ -121,7 +123,7 @@ export class LaunchpadItemNode extends CacheableChildrenViewNode<'launchpad-item
121123
export class LaunchpadViewNode extends CacheableChildrenViewNode<
122124
'launchpad',
123125
LaunchpadView,
124-
GroupingNode | LaunchpadItemNode
126+
GroupingNode | LaunchpadItemNode | MessageNode
125127
> {
126128
private disposable: Disposable;
127129

@@ -151,7 +153,31 @@ export class LaunchpadViewNode extends CacheableChildrenViewNode<
151153
this.children = undefined;
152154
}
153155

154-
async getChildren(): Promise<(GroupingNode | LaunchpadItemNode)[]> {
156+
private createErrorNode(error: Error): MessageNode {
157+
// Extract AuthenticationError from AggregateError if present
158+
let actualError = error;
159+
if (error instanceof AggregateError) {
160+
const firstAuthError = error.errors.find(e => e instanceof AuthenticationError);
161+
actualError = firstAuthError ?? error.errors[0] ?? error;
162+
}
163+
164+
const isAuthError = actualError instanceof AuthenticationError;
165+
const contextValue = isAuthError ? ContextValues.LaunchpadErrorAuth : ContextValues.LaunchpadError;
166+
167+
return new MessageNode(
168+
this.view,
169+
this,
170+
isAuthError ? 'Authentication Required' : 'Unable to fully load items',
171+
actualError.name === 'HttpError' && 'status' in actualError && typeof actualError.status === 'number'
172+
? `${actualError.status}: ${String(actualError)}`
173+
: String(actualError),
174+
String(actualError),
175+
new ThemeIcon('warning', new ThemeColor('list.warningForeground')),
176+
contextValue,
177+
);
178+
}
179+
180+
async getChildren(): Promise<(GroupingNode | LaunchpadItemNode | MessageNode)[]> {
155181
this.view.description = this.view.grouped
156182
? `${this.view.name.toLocaleLowerCase()}\u00a0\u2022\u00a0 ${proBadge}`
157183
: proBadge;
@@ -161,13 +187,25 @@ export class LaunchpadViewNode extends CacheableChildrenViewNode<
161187
const access = await this.view.container.git.access('launchpad');
162188
if (!access.allowed) return [];
163189

164-
const children: (GroupingNode | LaunchpadItemNode)[] = [];
190+
const children: (GroupingNode | LaunchpadItemNode | MessageNode)[] = [];
165191

166192
const hasIntegrations = await this.view.container.launchpad.hasConnectedIntegration();
167193
if (!hasIntegrations) return [];
168194

169195
try {
170196
const result = await this.view.container.launchpad.getCategorizedItems();
197+
198+
// Handle error - always show error node
199+
if (result.error != null) {
200+
this.view.message = undefined;
201+
children.push(this.createErrorNode(result.error));
202+
203+
// If there are no items, just return the error node
204+
if (!result.items?.length) {
205+
return children;
206+
}
207+
}
208+
171209
if (!result.items?.length) {
172210
this.view.message = 'All done! Take a vacation.';
173211
return [];

src/views/nodes/abstract/viewNode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export const enum ContextValues {
6767
FileHistory = 'gitlens:history:file',
6868
Folder = 'gitlens:folder',
6969
Grouping = 'gitlens:grouping',
70+
LaunchpadError = 'gitlens:launchpad:error',
71+
LaunchpadErrorAuth = 'gitlens:launchpad:error+auth',
7072
LaunchpadItem = 'gitlens:launchpad:item',
7173
LineHistory = 'gitlens:history:line',
7274
MergeConflictCurrentChanges = 'gitlens:merge-conflict:current',

0 commit comments

Comments
 (0)