Skip to content

Commit ec0e17a

Browse files
adamintCopilot
andcommitted
Address dashboard launch review feedback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent eb0965a commit ec0e17a

7 files changed

Lines changed: 161 additions & 39 deletions

File tree

extension/loc/xlf/aspire-vscode.xlf

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extension/package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@
212212
"walkthrough.getStarted.createProject.title": "Create a new project",
213213
"walkthrough.getStarted.createProject.description": "Scaffold a new Aspire project from a starter template. The template includes an AppHost orchestrator, a sample API, and a web frontend.\n\n[Create new project](command:aspire-vscode.new)",
214214
"walkthrough.getStarted.runApp.title": "Run your app",
215-
"walkthrough.getStarted.runApp.description": "Start your Aspire app to launch all services and open the real-time dashboard.\n\n[Run AppHost](command:aspire-vscode.runAppHostCommand)\n\n[Debug AppHost](command:aspire-vscode.debugAppHostCommand)",
215+
"walkthrough.getStarted.runApp.description": "Start your Aspire app to launch all services. The dashboard stays available from the Aspire panel when you need it.\n\n[Run AppHost](command:aspire-vscode.runAppHostCommand)\n\n[Debug AppHost](command:aspire-vscode.debugAppHostCommand)",
216216
"walkthrough.getStarted.dashboard.title": "Explore the dashboard",
217217
"walkthrough.getStarted.dashboard.description": "The Aspire Dashboard shows your resources, endpoints, logs, traces, and metrics \u2014 all in one place.\n\n[Open dashboard](command:aspire-vscode.openDashboard)",
218218
"walkthrough.getStarted.nextSteps.title": "Next steps",

extension/src/server/interactionService.ts

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { extensionLogOutputChannel } from '../utils/logging';
1010
import { AspireExtendedDebugConfiguration, EnvVar } from '../dcp/types';
1111
import { AnsiColors, AspireTerminal } from '../utils/AspireTerminalProvider';
1212
import { AspireDebugSession } from '../debugger/AspireDebugSession';
13-
import type { DashboardBrowserType, DashboardLaunchBehavior } from '../debugger/AspireDebugSession';
13+
import type { DashboardLaunchBehavior } from '../debugger/AspireDebugSession';
1414
import { isDirectory } from '../utils/io';
1515

1616
export interface IInteractionService {
@@ -44,6 +44,11 @@ export interface IInteractionService {
4444

4545
type CSLogLevel = 'Trace' | 'Debug' | 'Information' | 'Warn' | 'Error' | 'Critical';
4646
const dashboardDefaultChangedNotificationKey = 'aspire.dashboardBrowser.defaultChangedNotification.v1';
47+
type DashboardLaunchBehaviorSource = 'debugConfiguration' | 'globalConfiguration' | 'legacyConfiguration' | 'default';
48+
type ResolvedDashboardLaunchBehavior = {
49+
behavior: DashboardLaunchBehavior;
50+
source: DashboardLaunchBehaviorSource;
51+
};
4752

4853
// Support both PascalCase (old) and camelCase (new) for backwards compatibility
4954
// with different versions of the CLI/AppHost.
@@ -132,15 +137,6 @@ function getConfiguredDashboardLaunchBehavior(aspireConfig: vscode.WorkspaceConf
132137
return normalizeDashboardLaunchBehavior(configuredValue);
133138
}
134139

135-
function hasConfiguredDashboardLaunchBehavior(aspireConfig: vscode.WorkspaceConfiguration): boolean {
136-
const dashboardBrowserInspection = aspireConfig.inspect<unknown>('dashboardBrowser');
137-
const dashboardBrowserConfigured = dashboardBrowserInspection?.workspaceFolderValue !== undefined
138-
|| dashboardBrowserInspection?.workspaceValue !== undefined
139-
|| dashboardBrowserInspection?.globalValue !== undefined;
140-
141-
return dashboardBrowserConfigured || getConfiguredLegacyDashboardLaunchBehavior(aspireConfig) !== undefined;
142-
}
143-
144140
// Support both PascalCase (old) and camelCase (new) for backwards compatibility.
145141
// DisplayLineState is serialized with ModelContextProtocol.McpJsonUtilities.DefaultOptions
146142
// which changed to camelCase in version 0.2.0+
@@ -412,17 +408,17 @@ export class InteractionService implements IInteractionService {
412408
const aspireConfig = vscode.workspace.getConfiguration('aspire');
413409
const dashboardLaunchBehavior = this.getDashboardLaunchBehavior(aspireConfig);
414410

415-
if (dashboardLaunchBehavior === 'none') {
416-
await this.showDashboardDefaultChangedNotificationIfNeeded(aspireConfig);
411+
if (dashboardLaunchBehavior.behavior === 'none') {
412+
await this.showDashboardDefaultChangedNotificationIfNeeded(dashboardLaunchBehavior.source);
417413
return;
418414
}
419415

420-
if (dashboardLaunchBehavior !== 'notification') {
416+
if (dashboardLaunchBehavior.behavior !== 'notification') {
421417
// Open the dashboard URL in the configured browser. Prefer codespaces URL if available.
422418
const urlToOpen = codespacesUrl || baseUrl;
423419
const debugSession = this._getAspireDebugSession();
424420
if (debugSession) {
425-
await debugSession.openDashboard(urlToOpen, dashboardLaunchBehavior);
421+
await debugSession.openDashboard(urlToOpen, dashboardLaunchBehavior.behavior);
426422
}
427423
return;
428424
}
@@ -457,61 +453,80 @@ export class InteractionService implements IInteractionService {
457453
vscode.env.openExternal(vscode.Uri.parse(codespacesUrl));
458454
}
459455
else if (selected.title === settingsLabel) {
460-
vscode.commands.executeCommand('workbench.action.openSettings', 'aspire.dashboardBrowser');
456+
this.openDashboardLaunchBehaviorSettings(dashboardLaunchBehavior.source);
461457
}
462458
});
463459
}, 1000);
464460
}
465461

466-
private getDashboardLaunchBehavior(aspireConfig: vscode.WorkspaceConfiguration): DashboardLaunchBehavior {
462+
private getDashboardLaunchBehavior(aspireConfig: vscode.WorkspaceConfiguration): ResolvedDashboardLaunchBehavior {
467463
const debugSession = this._getAspireDebugSession();
468464
const debugConfigurationBehavior = normalizeDashboardLaunchBehavior(debugSession?.configuration.dashboardBrowser);
469465
if (debugConfigurationBehavior) {
470-
return debugConfigurationBehavior;
466+
return { behavior: debugConfigurationBehavior, source: 'debugConfiguration' };
471467
}
472468

473469
const configuredGlobalBehavior = getConfiguredDashboardLaunchBehavior(aspireConfig);
474470
if (configuredGlobalBehavior === 'none' || configuredGlobalBehavior === 'notification') {
475-
return configuredGlobalBehavior;
471+
return { behavior: configuredGlobalBehavior, source: 'globalConfiguration' };
476472
}
477473

478474
const legacyBehavior = getConfiguredLegacyDashboardLaunchBehavior(aspireConfig);
479475

480476
if (legacyBehavior) {
481477
if (legacyBehavior === 'notification' || legacyBehavior === 'none') {
482-
return legacyBehavior;
478+
return { behavior: legacyBehavior, source: 'legacyConfiguration' };
483479
}
484480

485-
return configuredGlobalBehavior ?? 'integratedBrowser';
481+
return {
482+
behavior: configuredGlobalBehavior ?? 'integratedBrowser',
483+
source: configuredGlobalBehavior ? 'globalConfiguration' : 'legacyConfiguration'
484+
};
486485
}
487486

488487
if (configuredGlobalBehavior) {
489-
return configuredGlobalBehavior;
488+
return { behavior: configuredGlobalBehavior, source: 'globalConfiguration' };
490489
}
491490

492-
return normalizeDashboardLaunchBehavior(aspireConfig.get<unknown>('dashboardBrowser', 'none')) ?? 'none';
491+
return {
492+
behavior: normalizeDashboardLaunchBehavior(aspireConfig.get<unknown>('dashboardBrowser', 'none')) ?? 'none',
493+
source: 'default'
494+
};
493495
}
494496

495-
private async showDashboardDefaultChangedNotificationIfNeeded(aspireConfig: vscode.WorkspaceConfiguration): Promise<void> {
497+
private async showDashboardDefaultChangedNotificationIfNeeded(source: DashboardLaunchBehaviorSource): Promise<void> {
496498
if (!this._globalState || this._globalState.get<boolean>(dashboardDefaultChangedNotificationKey, false)) {
497499
return;
498500
}
499501

500-
if (hasConfiguredDashboardLaunchBehavior(aspireConfig)) {
502+
if (source !== 'default') {
501503
return;
502504
}
503505

504506
await this._globalState.update(dashboardDefaultChangedNotificationKey, true);
505507
vscode.window.showInformationMessage(dashboardLaunchBehaviorChanged, settingsLabel, changelogLabel).then(selected => {
506508
if (selected === settingsLabel) {
507-
vscode.commands.executeCommand('workbench.action.openSettings', 'aspire.dashboardBrowser');
509+
this.openDashboardLaunchBehaviorSettings(source);
508510
}
509511
else if (selected === changelogLabel) {
510512
vscode.env.openExternal(vscode.Uri.parse('https://github.com/microsoft/aspire/blob/main/extension/CHANGELOG.md'));
511513
}
512514
});
513515
}
514516

517+
private openDashboardLaunchBehaviorSettings(source: DashboardLaunchBehaviorSource): void {
518+
if (source === 'debugConfiguration') {
519+
vscode.commands.executeCommand('workbench.action.debug.configure');
520+
return;
521+
}
522+
523+
vscode.commands.executeCommand(
524+
'workbench.action.openSettings',
525+
source === 'legacyConfiguration'
526+
? 'aspire.enableAspireDashboardAutoLaunch'
527+
: 'aspire.dashboardBrowser');
528+
}
529+
515530
async displayLines(lines: ConsoleLine[]) {
516531
this.clearProgressNotification();
517532

extension/src/test-e2e/commandPalette.e2e.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from 'path';
44
import { getCommandInvocationCount, getTerminalCommandCount, isSamePath, waitForCommandOutcome, waitForExtensionState, waitForRepositoryIdle, waitForTerminalCommand, waitForWorkspaceAppHost } from './helpers/assertions';
55
import { createAdditionalAppHostCandidate, executeE2eControlCommand, removeAdditionalAppHostCandidate, removeWorkspaceAppHostConfig, restoreE2eCliPathForE2E, restoreWorkspaceAppHostConfig, restoreWorkspaceCliPath, runE2eTeardown, setCliUnavailableForE2E, setE2eCliPathForE2E, setTerminalCommandExecutionSuppressedForE2E, writeWorkspaceCliPath } from './helpers/fixtures';
66
import { getWorkspaceRoot } from './helpers/paths';
7-
import { executeCommandFromPalette, openAspireView, waitForEditorTitle, waitForNotificationMessage, waitForTerminalChannel, waitForWorkbenchText } from './helpers/vscode';
7+
import { chooseActiveQuickPick, executeCommandFromPalette, openAspireView, waitForEditorTitle, waitForNotificationMessage, waitForTerminalChannel, waitForWorkbenchText } from './helpers/vscode';
88

99
suite('Aspire command palette E2E', function () {
1010
this.timeout(420000);
@@ -84,12 +84,13 @@ suite('Aspire command palette E2E', function () {
8484

8585
const configureBefore = getCommandInvocationCount('aspire-vscode.configureLaunchJson');
8686
await executeCommandFromPalette('Aspire: Configure launch.json file');
87+
await chooseActiveQuickPick('Do not open the dashboard');
8788
await waitForCommandOutcome('aspire-vscode.configureLaunchJson', 'success', 60000, configureBefore);
8889
assert.ok((await waitForEditorTitle('launch.json')).includes('launch.json'));
8990

9091
const launchJsonPath = path.join(getWorkspaceRoot(), '.vscode', 'launch.json');
91-
const launchJson = JSON.parse(fs.readFileSync(launchJsonPath, 'utf8')) as { configurations?: Array<{ type?: string; request?: string }> };
92-
assert.ok(launchJson.configurations?.some(configuration => configuration.type === 'aspire' && configuration.request === 'launch'));
92+
const launchJson = JSON.parse(fs.readFileSync(launchJsonPath, 'utf8')) as { configurations?: Array<{ type?: string; request?: string; dashboardBrowser?: string }> };
93+
assert.ok(launchJson.configurations?.some(configuration => configuration.type === 'aspire' && configuration.request === 'launch' && configuration.dashboardBrowser === 'none'));
9394
});
9495

9596
test('observes multiple AppHost candidates without selecting the wrong one', async () => {

extension/src/test-e2e/debugDashboard.e2e.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as assert from 'assert';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import { getCommandInvocationCount, getDebugLaunchCount, getTreeAppHostLabel, isSamePath, waitForAppHostLaunching, waitForCommandOutcome, waitForDebugConsoleOutput, waitForDebugDashboardUrl, waitForDebugLaunch, waitForDebugSessionStartup, waitForExtensionState, waitForHttpText, waitForNoDebugSessions, waitForNoRunningAppHost, waitForRepositoryIdle, waitForRunningAppHost, waitForWorkspaceAppHost } from './helpers/assertions';
5-
import { executeE2eControlCommand, restoreWorkspaceCliPath, runE2eTeardown, setCliUnavailableForE2E, setShowStatusDelayForE2E, stopPrimaryAppHostIfRunning, writeFileWithRetry } from './helpers/fixtures';
5+
import { executeE2eControlCommand, restoreWorkspaceCliPath, runE2eTeardown, setCliUnavailableForE2E, setShowStatusDelayForE2E, stopPrimaryAppHostIfRunning, writeFileWithRetry, writeWorkspaceSetting } from './helpers/fixtures';
66
import { getPrimaryAppHostProjectPath } from './helpers/paths';
77
import { openAspireView, waitForEditorTitle, waitForTreeItem, waitForWorkbenchTextAfterIntegratedBrowserNavigation } from './helpers/vscode';
88

@@ -13,6 +13,7 @@ suite('Aspire debug dashboard E2E', function () {
1313
await runE2eTeardown([
1414
() => setCliUnavailableForE2E(false),
1515
() => setShowStatusDelayForE2E(undefined),
16+
() => writeWorkspaceSetting('aspire.dashboardBrowser', undefined),
1617
() => restoreWorkspaceCliPath(),
1718
() => executeE2eControlCommand({ name: 'stopDebugging' }),
1819
() => stopPrimaryAppHostIfRunning(),
@@ -22,6 +23,7 @@ suite('Aspire debug dashboard E2E', function () {
2223
});
2324

2425
test('debugs the AppHost and opens the dashboard in the integrated browser', async () => {
26+
writeWorkspaceSetting('aspire.dashboardBrowser', 'integratedBrowser');
2527
await openAspireView();
2628
await waitForRepositoryIdle();
2729
const discovered = await waitForWorkspaceAppHost();

extension/src/test-e2e/edgeCases.e2e.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { AspireExtensionE2EControlCommand } from '../types/extensionApi';
55
import { getCommandInvocationCount, getDebugLaunchCount, isSamePath, waitForCommandOutcome, waitForDebugLaunch, waitForExtensionState, waitForRepositoryIdle, waitForWorkspaceAppHost } from './helpers/assertions';
66
import { executeE2eControlCommand, restoreWorkspaceCliPath, runE2eTeardown, setCliUnavailableForE2E, setDebugLaunchSuppressedForE2E, stopPrimaryAppHostIfRunning } from './helpers/fixtures';
77
import { getPrimaryAppHostProjectPath, getWorkspaceRoot } from './helpers/paths';
8-
import { openAspireView } from './helpers/vscode';
8+
import { chooseActiveQuickPick, openAspireView } from './helpers/vscode';
99

1010
suite('Aspire extension edge case E2E', function () {
1111
this.timeout(180000);
@@ -61,12 +61,13 @@ suite('Aspire extension edge case E2E', function () {
6161
await waitForCommandOutcome('aspire-vscode.settings', 'success', 60000, settingsBefore);
6262

6363
const configureBefore = getCommandInvocationCount('aspire-vscode.configureLaunchJson');
64-
await executeE2eControlCommand({ name: 'executeAspireCommand', commandId: 'aspire-vscode.configureLaunchJson' });
64+
await executeE2eControlCommand({ name: 'executeAspireCommand', commandId: 'aspire-vscode.configureLaunchJson' }, { waitFor: 'started' });
65+
await chooseActiveQuickPick('Do not open the dashboard');
6566
await waitForCommandOutcome('aspire-vscode.configureLaunchJson', 'success', 60000, configureBefore);
6667

6768
const launchJsonPath = path.join(getWorkspaceRoot(), '.vscode', 'launch.json');
68-
const launchJson = JSON.parse(fs.readFileSync(launchJsonPath, 'utf8')) as { configurations?: Array<{ type?: string }> };
69-
assert.ok(launchJson.configurations?.some(configuration => configuration.type === 'aspire'));
69+
const launchJson = JSON.parse(fs.readFileSync(launchJsonPath, 'utf8')) as { configurations?: Array<{ type?: string; dashboardBrowser?: string }> };
70+
assert.ok(launchJson.configurations?.some(configuration => configuration.type === 'aspire' && configuration.dashboardBrowser === 'none'));
7071
});
7172

7273
test('clears launch state after suppressed debug launch requests', async () => {

0 commit comments

Comments
 (0)