Skip to content

Commit

Permalink
Add resource relationship to app model
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Oct 9, 2024
1 parent 87827cf commit 82aa476
Show file tree
Hide file tree
Showing 37 changed files with 559 additions and 15 deletions.
51 changes: 51 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using Aspire.Dashboard.Components.Controls.Grid
@using Aspire.Dashboard.Model
@using Aspire.Dashboard.Otlp.Model
@using Aspire.Dashboard.Resources
@using Aspire.Dashboard.Utils
@using Humanizer
Expand Down Expand Up @@ -111,6 +112,56 @@
</PropertyGrid>
</FluentAccordionItem>
}
<FluentAccordionItem Heading="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsReferences)]" Expanded="true">
<div slot="end">
<FluentBadge Appearance="Appearance.Neutral" Circular="true">
@FilteredRelationships.Count()
</FluentBadge>
</div>
<FluentDataGrid TGridItem="ResourceDetailRelationship"
Items="@FilteredRelationships"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 0.75fr 0.75fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsResourceHeader)]">
<span style="padding-left:5px; border-left-width: 5px; border-left-style: solid; border-left-color: @(ColorGenerator.Instance.GetColorHexByKey(context.ResourceName));">
@context.ResourceName
</span>
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsTypeHeader)]">
@context.Type
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ViewAction)]">
<FluentButton Appearance="Appearance.Lightweight" OnClick="@(() => OnViewRelationshipAsync(context))">@ControlStringsLoc[nameof(ControlsStrings.ViewAction)]</FluentButton>
</TemplateColumn>
</FluentDataGrid>
</FluentAccordionItem>
<FluentAccordionItem Heading="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsBackReferences)]" Expanded="true">
<div slot="end">
<FluentBadge Appearance="Appearance.Neutral" Circular="true">
@FilteredBackRelationships.Count()
</FluentBadge>
</div>
<FluentDataGrid TGridItem="ResourceDetailRelationship"
Items="@FilteredBackRelationships"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 0.75fr 0.75fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsResourceHeader)]">
<span style="padding-left:5px; border-left-width: 5px; border-left-style: solid; border-left-color: @(ColorGenerator.Instance.GetColorHexByKey(context.ResourceName));">
@context.ResourceName
</span>
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ResourceDetailsTypeHeader)]">
@context.Type
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.ViewAction)]">
<FluentButton Appearance="Appearance.Lightweight" OnClick="@(() => OnViewRelationshipAsync(context))">@ControlStringsLoc[nameof(ControlsStrings.ViewAction)]</FluentButton>
</TemplateColumn>
</FluentDataGrid>
</FluentAccordionItem>
<FluentAccordionItem Heading="@ControlStringsLoc[nameof(ControlsStrings.ResourceHealthChecksHeader)]" Expanded="@_isHealthChecksExpanded">
<div slot="end">
<FluentBadge Appearance="Appearance.Neutral" Circular="true">
Expand Down
100 changes: 99 additions & 1 deletion src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Utils;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
Expand All @@ -13,6 +15,9 @@ public partial class ResourceDetails
[Parameter, EditorRequired]
public required ResourceViewModel Resource { get; set; }

[Parameter]
public required ConcurrentDictionary<string, ResourceViewModel> ResourceByName { get; set; }

[Parameter]
public bool ShowSpecOnlyToggle { get; set; }

Expand All @@ -22,6 +27,9 @@ public partial class ResourceDetails
[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }

[Inject]
public required NavigationManager NavigationManager { get; init; }

private bool IsSpecOnlyToggleDisabled => !Resource.Environment.All(i => !i.FromSpec) && !GetResourceProperties(ordered: false).Any(static vm => vm.KnownProperty is null);

// NOTE Excludes endpoints as they don't expose sensitive items (and enumerating endpoints is non-trivial)
Expand All @@ -40,6 +48,16 @@ public partial class ResourceDetails
.Where(vm => vm.MatchesFilter(_filter))
.AsQueryable();

private IQueryable<ResourceDetailRelationship> FilteredRelationships =>
GetRelationships()
.Where(vm => vm.MatchesFilter(_filter))
.AsQueryable();

private IQueryable<ResourceDetailRelationship> FilteredBackRelationships =>
GetBackRelationships()
.Where(vm => vm.MatchesFilter(_filter))
.AsQueryable();

private IQueryable<VolumeViewModel> FilteredVolumes =>
Resource.Volumes
.Where(vm => vm.MatchesFilter(_filter))
Expand Down Expand Up @@ -84,6 +102,68 @@ protected override void OnParametersSet()
}
}

private IEnumerable<ResourceDetailRelationship> GetRelationships()
{
if (ResourceByName == null)
{
return [];
}

var items = new List<ResourceDetailRelationship>();

foreach (var relationship in Resource.Relationships)
{
var matches = ResourceByName.Values
.Where(r => string.Equals(r.DisplayName, relationship.ResourceName, StringComparisons.ResourceName))
.Where(r => r.KnownState != KnownResourceState.Hidden)
.ToList();

foreach (var match in matches)
{
items.Add(new()
{
Resource = match,
ResourceName = ResourceViewModel.GetResourceName(match, ResourceByName),
Type = relationship.Type
});
}
}

return items;
}

private IEnumerable<ResourceDetailRelationship> GetBackRelationships()
{
if (ResourceByName == null)
{
return [];
}

var items = new List<ResourceDetailRelationship>();

var otherResources = ResourceByName.Values
.Where(r => r != Resource)
.Where(r => r.KnownState != KnownResourceState.Hidden);

foreach (var otherResource in otherResources)
{
foreach (var relationship in otherResource.Relationships)
{
if (string.Equals(relationship.ResourceName, Resource.DisplayName, StringComparisons.ResourceName))
{
items.Add(new()
{
Resource = otherResource,
ResourceName = ResourceViewModel.GetResourceName(otherResource, ResourceByName),
Type = relationship.Type
});
}
}
}

return items.OrderBy(r => r.ResourceName);
}

private List<DisplayedEndpoint> GetEndpoints()
{
return ResourceEndpointHelpers.GetEndpoints(Resource, includeInternalUrls: true);
Expand Down Expand Up @@ -119,7 +199,25 @@ private void OnValueMaskedChanged()
return;
}
}

_isMaskAllChecked = true;
}

public Task OnViewRelationshipAsync(ResourceDetailRelationship relationship)
{
NavigationManager.NavigateTo(DashboardUrls.ResourcesUrl(resource: relationship.Resource.Name));
return Task.CompletedTask;
}
}

public sealed class ResourceDetailRelationship
{
public required ResourceViewModel Resource { get; init; }
public required string ResourceName { get; init; }
public required string Type { get; set; }

public bool MatchesFilter(string filter)
{
return Resource.DisplayName.Contains(filter, StringComparison.CurrentCultureIgnoreCase) ||
Type.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
}
}
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
</GridColumnManager>
</Summary>
<Details>
<ResourceDetails Resource="context" ShowSpecOnlyToggle="true" />
<ResourceDetails Resource="context" ResourceByName="_resourceByName" ShowSpecOnlyToggle="true" />
</Details>
</SummaryDetailsView>
</MainSection>
Expand Down
18 changes: 18 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public partial class Resources : ComponentBase, IAsyncDisposable
[SupplyParameterFromQuery]
public string? VisibleTypes { get; set; }

[Parameter]
[SupplyParameterFromQuery(Name = "resource")]
public string? ResourceName { get; set; }

private ResourceViewModel? SelectedResource { get; set; }

private readonly CancellationTokenSource _watchTaskCancellationTokenSource = new();
Expand Down Expand Up @@ -251,6 +255,20 @@ private void UpdateMaxHighlightedCount()
_maxHighlightedCount = Math.Min(maxHighlightedCount, 2);
}

protected override async Task OnParametersSetAsync()
{
if (ResourceName is not null)
{
if (_resourceByName.TryGetValue(ResourceName, out var selectedResource))
{
await ShowResourceDetailsAsync(selectedResource, buttonId: null);
}

// Navigate to remove ?resource=xxx in the URL.
NavigationManager.NavigateTo(DashboardUrls.ResourcesUrl(), new NavigationOptions { ReplaceHistoryEntry = true });
}
}

private bool ApplicationErrorCountsChanged(Dictionary<ApplicationKey, int> newApplicationUnviewedErrorCounts)
{
if (_applicationUnviewedErrorCounts == null || _applicationUnviewedErrorCounts.Count != newApplicationUnviewedErrorCounts.Count)
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public partial class TraceDetail : ComponentBase

[Parameter]
[SupplyParameterFromQuery]
public required string? SpanId { get; set; }
public string? SpanId { get; set; }

[Inject]
public required TelemetryRepository TelemetryRepository { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

@inject IStringLocalizer<Columns> Loc

<span title="@FormatName(Resource)"><FluentHighlighter HighlightedText="@FilterText" Text="@FormatName(Resource)" /></span>
<span style="padding-left:5px; border-left-width: 5px; border-left-style: solid; border-left-color: @(ColorGenerator.Instance.GetColorHexByKey(FormatName(Resource)));">
<span title="@FormatName(Resource)"><FluentHighlighter HighlightedText="@FilterText" Text="@FormatName(Resource)"/></span>
</span>

@if (Resource.Properties.TryGetValue(KnownProperties.Container.Lifetime, out var value) &&
value.Value.HasStringValue &&
Expand Down
17 changes: 17 additions & 0 deletions src/Aspire.Dashboard/Model/ResourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public sealed class ResourceViewModel
public required ImmutableArray<EnvironmentVariableViewModel> Environment { get; init; }
public required ImmutableArray<UrlViewModel> Urls { get; init; }
public required ImmutableArray<VolumeViewModel> Volumes { get; init; }
public required ImmutableArray<RelationshipViewModel> Relationships { get; init; }
public required FrozenDictionary<string, ResourcePropertyViewModel> Properties { get; init; }
public required ImmutableArray<CommandViewModel> Commands { get; init; }
/// <summary>The health status of the resource. <see langword="null"/> indicates that health status is expected but not yet available.</summary>
Expand Down Expand Up @@ -323,3 +324,19 @@ public bool MatchesFilter(string filter)
_humanizedHealthStatus.Contains(filter, StringComparison.OrdinalIgnoreCase);
}
}

[DebuggerDisplay("ResourceName = {ResourceName}, Type = {Type}")]
public sealed class RelationshipViewModel
{
public string ResourceName { get; }
public string Type { get; }

public RelationshipViewModel(string resourceName, string type)
{
ArgumentException.ThrowIfNullOrWhiteSpace(resourceName);
ArgumentException.ThrowIfNullOrWhiteSpace(type);

ResourceName = resourceName;
Type = type;
}
}
8 changes: 8 additions & 0 deletions src/Aspire.Dashboard/ResourceService/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public ResourceViewModel ToViewModel(BrowserTimeProvider timeProvider, IKnownPro
Environment = GetEnvironment(),
Urls = GetUrls(),
Volumes = GetVolumes(),
Relationships = GetRelationships(),
State = HasState ? State : null,
KnownState = HasState ? Enum.TryParse(State, out KnownResourceState knownState) ? knownState : null : null,
StateStyle = HasStateStyle ? StateStyle : null,
Expand Down Expand Up @@ -81,6 +82,13 @@ ImmutableArray<EnvironmentVariableViewModel> GetEnvironment()
.ToImmutableArray();
}

ImmutableArray<RelationshipViewModel> GetRelationships()
{
return Relationships
.Select(r => new RelationshipViewModel(r.ResourceName, r.Type))
.ToImmutableArray();
}

ImmutableArray<UrlViewModel> GetUrls()
{
// Filter out bad urls
Expand Down
27 changes: 27 additions & 0 deletions src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src/Aspire.Dashboard/Resources/ControlsStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,13 @@
<data name="ActionStructuredLogsText" xml:space="preserve">
<value>Structured logs</value>
</data>
</root>
<data name="ResourceDetailsTypeHeader" xml:space="preserve">
<value>Type</value>
</data>
<data name="ResourceDetailsReferences" xml:space="preserve">
<value>References</value>
</data>
<data name="ResourceDetailsBackReferences" xml:space="preserve">
<value>Back references</value>
</data>
</root>
Loading

0 comments on commit 82aa476

Please sign in to comment.