Skip to content

Fix describe duplicate rows and colorize resource name in describe#14803

Merged
JamesNK merged 4 commits intorelease/13.2from
jamesnk/describe-duplicates
Mar 1, 2026
Merged

Fix describe duplicate rows and colorize resource name in describe#14803
JamesNK merged 4 commits intorelease/13.2from
jamesnk/describe-duplicates

Conversation

@JamesNK
Copy link
Member

@JamesNK JamesNK commented Feb 28, 2026

Description

Fixes #14405

  • Track last content written for both JSON and human readable formats. Don't write duplicate entries
  • Colorize resource name so it's consistent with aspire logs
  • Remove special case in aspire logs that skips ANSI characters. This feature is built into Spectre.Console

After:
image

Also add colors to table:
image

I think colors need a tweak in how they're displayed, but it's a starting point.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

@github-actions
Copy link
Contributor

github-actions bot commented Feb 28, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14803

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14803"

@github-actions
Copy link
Contributor

github-actions bot commented Feb 28, 2026

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit 013379c:

Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AgentInitCommand_WithMalformedMcpJson_ShowsErrorAndExitsNonZero ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateStartWaitAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ❌ Upload failed

📹 Recordings uploaded automatically from CI run #22537643658

@davidfowl
Copy link
Member

Why would we show a stream of resource updates for all running apphosts? Or is that text incorrect?

@JamesNK JamesNK marked this pull request as ready for review February 28, 2026 20:49
Copilot AI review requested due to automatic review settings February 28, 2026 20:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Aspire CLI describe/resources and logs output to reduce watch-mode noise and make resource name coloring consistent across commands (fixing #14405).

Changes:

  • Add per-resource deduplication in describe --follow for both JSON (NDJSON) and human-readable output.
  • Introduce a shared ResourceColorMap and apply resource-name coloring in describe and logs.
  • Adjust CLI test infrastructure to support disabling ANSI/markup rendering for stable assertions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Adds test option to disable ANSI and improves writer line buffering to better capture Spectre output.
tests/Aspire.Cli.Tests/Commands/LogsCommandTests.cs Updates tests to disable ANSI via test options instead of NO_COLOR.
tests/Aspire.Cli.Tests/Commands/DescribeCommandTests.cs Adds follow-mode deduplication tests for JSON and table/text output.
src/Aspire.Cli/Utils/ResourceColorMap.cs New shared utility to assign stable colors per resource name.
src/Aspire.Cli/Commands/LogsCommand.cs Switches to shared color mapping and relies on Spectre handling for ANSI/no-color behavior.
src/Aspire.Cli/Commands/DescribeCommand.cs Adds watch-mode output dedup + colorized names in follow output and tables.

Comment on lines 194 to 210
// Maintain a list of all resources seen so far for relationship resolution.
// Seed with a snapshot so that display names resolve correctly from the start.
var allResources = await connection.GetResourceSnapshotsAsync(cancellationToken).ConfigureAwait(false);

// Cache the last displayed content per resource to avoid duplicate output.
// Values are either a string (JSON mode) or a ResourceDisplayState (non-JSON mode).
var lastDisplayedContent = new Dictionary<string, object>(StringComparers.ResourceName);

// Stream resource snapshots
await foreach (var snapshot in connection.WatchResourceSnapshotsAsync(cancellationToken).ConfigureAwait(false))
{
// Update the dictionary with the latest snapshot for this resource
allResources[snapshot.Name] = snapshot;

// Filter by resource name if specified
if (resourceName is not null)
{
var resolved = ResourceSnapshotMapper.ResolveResources(resourceName, allResources.Values.ToList());
var resolved = ResourceSnapshotMapper.ResolveResources(resourceName, allResources);
if (!resolved.Any(r => string.Equals(r.Name, snapshot.Name, StringComparison.OrdinalIgnoreCase)))
{
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In watch mode, allResources is initialized as the full snapshot list and then reused unchanged. This breaks name resolution and filtering because ResolveResources() and GetResourceName() assume the collection contains one entry per resource (current state), not a growing history. With multiple snapshots for the same resource, ResolveResources() can stop matching on DisplayName, and GetResourceName() can incorrectly think replicas exist and fall back to resource.Name. Consider keeping allResources as a dictionary keyed by snapshot.Name (seeded from GetResourceSnapshotsAsync()), and update/insert on each watched snapshot before calling ResolveResources() / MapToResourceJson() / BuildResourceDisplayState().

Copilot uses AI. Check for mistakes.
Comment on lines 295 to 299
_ => health
};

table.AddRow(displayName, type, stateText, healthText, endpoints);
table.AddRow($"[{_resourceColorMap.GetColor(displayName)}]{displayName.EscapeMarkup()}[/]", type, stateText, healthText, endpoints);
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

table.AddRow(...) mixes Spectre markup cells (e.g., stateText/healthText and the colorized name) with unescaped dynamic strings (type, endpoints, and the underlying state/health values inside the markup). Because Table.AddRow(string, ...) treats strings as markup, values like IPv6 endpoints (http://[::1]:...) or unexpected characters in state/health can be parsed as markup and throw or render incorrectly. Escape non-markup values (and escape state/health before wrapping them in color tags) when building the row.

Copilot uses AI. Check for mistakes.
@JamesNK JamesNK changed the title Colorize resource name in describe Fix describe duplicate rows and colorize resource name in describe Feb 28, 2026
@JamesNK
Copy link
Member Author

JamesNK commented Feb 28, 2026

Why would we show a stream of resource updates for all running apphosts? Or is that text incorrect?

The message "showing all running app hosts" is displayed before selecting a running app host. Can improve output by displaying the selected app host.

@JamesNK
Copy link
Member Author

JamesNK commented Mar 1, 2026

Results of select now displayed:

image

@JamesNK JamesNK force-pushed the jamesnk/describe-duplicates branch from d465cd6 to 2d65653 Compare March 1, 2026 00:38
@JamesNK JamesNK merged commit d613c7f into release/13.2 Mar 1, 2026
345 checks passed
@JamesNK JamesNK deleted the jamesnk/describe-duplicates branch March 1, 2026 08:50
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants