Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metric highlights #5750

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2537f0f
Add "dashpage" as known dictionary word
drewnoakes Jul 25, 2024
d4eb709
Add "plotly" as a spelling word
drewnoakes Jul 26, 2024
816fe70
Refactor markup
drewnoakes Aug 13, 2024
f582577
Show dashpages in the metrics tree view
drewnoakes Aug 13, 2024
e1be2e3
Add API doc
drewnoakes Aug 13, 2024
4a2840a
Add definition types for dashpage configuration
drewnoakes Aug 13, 2024
06c2867
Add a collection of dashpages, and a selected dashpage
drewnoakes Aug 13, 2024
2abe4c3
Allow a dashpage to be selected in the tree
drewnoakes Aug 13, 2024
27fb380
Support rendering dashpages in content area
drewnoakes Aug 13, 2024
ce1d94c
Add TODO and placeholder for DashpageChart component
drewnoakes Aug 13, 2024
8f6a9a3
Bump log messages to warnings
drewnoakes Aug 13, 2024
28aa1f9
Make chart sizes responsive
drewnoakes Aug 13, 2024
31d0813
add DashpageCharts component
adamint Aug 13, 2024
52b2616
add chartwrapper and tablewrapper components
adamint Aug 13, 2024
90c98fb
Populate dashpages from configuration (#5302)
drewnoakes Aug 16, 2024
c5e2cb6
Update default dashpages (#5320)
drewnoakes Aug 16, 2024
4e83cf8
isolate chartcontainer logic
adamint Aug 19, 2024
615bbb7
add ellipsis to dashpage instrument title
adamint Aug 19, 2024
06532fb
Filter dashpages to show only available ones (#5337)
drewnoakes Aug 20, 2024
b298951
Merge branch 'feature/dashpages' of https://github.com/dotnet/aspire …
adamint Sep 9, 2024
6bf2014
show filter in popup
adamint Sep 9, 2024
7b33d02
Merge branch 'main' into feature/dashpages
adamint Sep 12, 2024
de2a719
fix merge issues
adamint Sep 12, 2024
cd6096f
dashpage ui work (#5693)
adamint Sep 17, 2024
938a79c
Merge remote-tracking branch 'upstream/main' into feature/dashpages
drewnoakes Sep 17, 2024
1bb988f
Formatting
drewnoakes Sep 17, 2024
8d5f414
Formatting
drewnoakes Sep 17, 2024
cc0ca71
Remove unused component
drewnoakes Sep 17, 2024
f3ace68
Required parameter updates
drewnoakes Sep 17, 2024
171653f
Remove commented code
drewnoakes Sep 17, 2024
cdd8b02
Remove unused variable
drewnoakes Sep 17, 2024
8ee4703
Use integer for unique element ID
drewnoakes Sep 17, 2024
71532b8
Remove incorrect comment
drewnoakes Sep 17, 2024
e6af39a
Remove unneeded file
drewnoakes Sep 17, 2024
19dfafd
Simplify expression
drewnoakes Sep 17, 2024
8a7cd75
[dashpages] Dashpage layout improvements (#5784)
drewnoakes Sep 20, 2024
4ad57a7
Merge branch main into feature/dashpages
drewnoakes Sep 25, 2024
ee92010
add header to dashpage desktop, add dashpage select page
adamint Sep 26, 2024
6181b89
Invoke state after tree item selected
Sep 30, 2024
7992a8d
Add spacing below dashpage charts, change filter to cog
Sep 30, 2024
17cd038
Re-add area restricted to the dash chart container. This parameter is…
Sep 30, 2024
c4adb9d
Initialize ShowCounts in chart filter on component initialization - s…
Sep 30, 2024
752e794
Add divider between chart type and time
Sep 30, 2024
93a27af
fix test by adding required member
Sep 30, 2024
d20f93b
Fix accessibility regression on mobile
Sep 30, 2024
394262a
Fix chart widths
Sep 30, 2024
f6c9ba9
Merge branch 'main' into feature/dashpages
adamint Oct 2, 2024
8fc9729
fix conflict
adamint Oct 2, 2024
a2c27cd
add import
Oct 2, 2024
df32795
additional dashpages changes (#6053)
adamint Oct 8, 2024
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
2 changes: 2 additions & 0 deletions spelling.dic
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ chmod
consolelogs
cors
csproj
dashpage
dashpages
dbug
dcpctrl
dockerfile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Resources;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Metrics = Aspire.Dashboard.Components.Pages.Metrics;

namespace Aspire.Dashboard.Components;

public partial class ChartContainer : ComponentBase, IAsyncDisposable
public abstract class ChartContainer : ComponentBase, IAsyncDisposable
{
private OtlpInstrumentData? _instrument;
private PeriodicTimer? _tickTimer;
private Task? _tickTask;
private IDisposable? _themeChangedSubscription;
private int _renderedDimensionsCount;
private readonly InstrumentViewModel _instrumentViewModel = new InstrumentViewModel();

[Parameter, EditorRequired]
public required ApplicationKey ApplicationKey { get; set; }
Expand All @@ -32,6 +31,9 @@ public partial class ChartContainer : ComponentBase, IAsyncDisposable
[Parameter, EditorRequired]
public required TimeSpan Duration { get; set; }

[Parameter, EditorRequired]
public required Func<Metrics.MetricViewKind, Task> OnViewChangedAsync { get; set; }

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

Expand All @@ -41,9 +43,14 @@ public partial class ChartContainer : ComponentBase, IAsyncDisposable
[Inject]
public required ThemeManager ThemeManager { get; init; }

[Inject]
public required IStringLocalizer<ControlsStrings> Loc { get; init; }

public List<DimensionFilterViewModel> DimensionFilters { get; } = [];
public string? PreviousMeterName { get; set; }
public string? PreviousInstrumentName { get; set; }
public InstrumentViewModel ViewModel { get; } = new();
public OtlpInstrumentData? InstrumentData { get; private set; }

protected override void OnInitialized()
{
Expand All @@ -52,7 +59,7 @@ protected override void OnInitialized()
_tickTask = Task.Run(UpdateDataAsync);
_themeChangedSubscription = ThemeManager.OnThemeChanged(async () =>
{
_instrumentViewModel.Theme = ThemeManager.Theme;
ViewModel.Theme = ThemeManager.Theme;
await InvokeAsync(StateHasChanged);
});
}
Expand All @@ -74,41 +81,41 @@ private async Task UpdateDataAsync()
var timer = _tickTimer;
while (await timer!.WaitForNextTickAsync())
{
_instrument = GetInstrument();
if (_instrument == null)
InstrumentData = GetInstrument();
if (InstrumentData == null)
{
continue;
}

if (_instrument.Dimensions.Count > _renderedDimensionsCount)
if (InstrumentData.Dimensions.Count > _renderedDimensionsCount)
{
// Re-render the entire control if the number of dimensions has changed.
_renderedDimensionsCount = _instrument.Dimensions.Count;
_renderedDimensionsCount = InstrumentData.Dimensions.Count;
await InvokeAsync(StateHasChanged);
}
else
{
await UpdateInstrumentDataAsync(_instrument);
await UpdateInstrumentDataAsync(InstrumentData);
}
}
}

public async Task DimensionValuesChangedAsync(DimensionFilterViewModel dimensionViewModel)
{
if (_instrument == null)
if (InstrumentData == null)
{
return;
}

await UpdateInstrumentDataAsync(_instrument);
await UpdateInstrumentDataAsync(InstrumentData);
}

private async Task UpdateInstrumentDataAsync(OtlpInstrumentData instrument)
{
var matchedDimensions = instrument.Dimensions.Where(MatchDimension).ToList();

// Only update data in plotly
await _instrumentViewModel.UpdateDataAsync(instrument.Summary, matchedDimensions);
await ViewModel.UpdateDataAsync(instrument.Summary, matchedDimensions);
}

private bool MatchDimension(DimensionScope dimension)
Expand Down Expand Up @@ -145,9 +152,9 @@ private static bool MatchFilter(KeyValuePair<string, string>[] attributes, Dimen

protected override async Task OnParametersSetAsync()
{
_instrument = GetInstrument();
InstrumentData = GetInstrument();

if (_instrument == null)
if (InstrumentData == null)
{
return;
}
Expand All @@ -161,7 +168,7 @@ protected override async Task OnParametersSetAsync()
DimensionFilters.Clear();
DimensionFilters.AddRange(filters);

await UpdateInstrumentDataAsync(_instrument);
await UpdateInstrumentDataAsync(InstrumentData);
}

private OtlpInstrumentData? GetInstrument()
Expand Down Expand Up @@ -195,9 +202,9 @@ protected override async Task OnParametersSetAsync()
private List<DimensionFilterViewModel> CreateUpdatedFilters(bool hasInstrumentChanged)
{
var filters = new List<DimensionFilterViewModel>();
if (_instrument != null)
if (InstrumentData != null)
{
foreach (var item in _instrument.KnownAttributeValues.OrderBy(kvp => kvp.Key))
foreach (var item in InstrumentData.KnownAttributeValues.OrderBy(kvp => kvp.Key))
{
var dimensionModel = new DimensionFilterViewModel
{
Expand Down Expand Up @@ -264,18 +271,4 @@ private List<DimensionFilterViewModel> CreateUpdatedFilters(bool hasInstrumentCh

return filters;
}

private Task OnTabChangeAsync(FluentTab newTab)
{
var id = newTab.Id?.Substring("tab-".Length);

if (id is null
|| !Enum.TryParse(typeof(Pages.Metrics.MetricViewKind), id, out var o)
|| o is not Pages.Metrics.MetricViewKind viewKind)
{
return Task.CompletedTask;
}

return OnViewChangedAsync(viewKind);
}
}
145 changes: 73 additions & 72 deletions src/Aspire.Dashboard/Components/Controls/Chart/ChartFilters.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,89 @@
@using Aspire.Dashboard.Components.Controls.Grid
@using Aspire.Dashboard.Resources
@using Aspire.Dashboard.Otlp.Model
@using Aspire.Dashboard.Model
@inject IStringLocalizer<ControlsStrings> Loc

<div class="metrics-filters-container">
<div class="@(IsRenderedInsideModal ? "metrics-filters-modal-container" : "metrics-filters-standalone-container")">
@if (DimensionFilters.Count > 0)
{
<div class="metrics-filters-section">
<h5>@Loc[nameof(ControlsStrings.ChartContainerFiltersHeader)]</h5>
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(Loc)"
ResizeType="DataGridResizeType.Discrete"
Items="@Queryable.AsQueryable(DimensionFilters)"
GridTemplateColumns="200px 1fr auto"
GenerateHeader="GenerateHeaderOption.None">
<ChildContent>
<AspirePropertyColumn Tooltip="true" TooltipText="@(c => c.Name)" Property="@(c => c.Name)"/>
<AspireTemplateColumn Tooltip="true" TooltipText="@(c => c.SelectedValues.Count == 0 ? Loc[nameof(ControlsStrings.LabelNone)] : string.Join(", ", c.SelectedValues.OrderBy(v => v.Text).Select(v => v.Text)))">
<FluentOverflow Class="dimension-overflow">
<ChildContent>
@if (context.SelectedValues.Count == 0)
{
<FluentBadge>@Loc[nameof(ControlsStrings.LabelNone)]</FluentBadge>
}
else
{
var i = 0;
foreach (var item in context.SelectedValues.OrderBy(v => v.Text))
<GridColumnManager @ref="_manager" Columns="@_gridColumns">
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(Loc)"
ResizeType="DataGridResizeType.Discrete"
Items="@Queryable.AsQueryable(DimensionFilters)"
GridTemplateColumns="@_manager.GetGridTemplateColumns()"
GenerateHeader="GenerateHeaderOption.None">
<ChildContent>
<AspirePropertyColumn ColumnManager="@_manager" ColumnId="@NameColumn" Tooltip="true" TooltipText="@(c => c.Name)" Property="@(c => c.Name)"/>
<AspireTemplateColumn ColumnManager="@_manager" ColumnId="@DimensionColumn" Tooltip="true" TooltipText="@(c => c.SelectedValues.Count == 0 ? Loc[nameof(ControlsStrings.LabelNone)] : string.Join(", ", c.SelectedValues.OrderBy(v => v.Text).Select(v => v.Text)))">
<FluentOverflow Class="dimension-overflow">
<ChildContent>
@if (context.SelectedValues.Count == 0)
{
// Always display the first item by setting a fixed value.
<FluentOverflowItem Fixed="@((i == 0) ? OverflowItemFixed.Ellipsis : OverflowItemFixed.None)">
<span class="dimension-tag">@item.Text</span>
</FluentOverflowItem>
i++;
<FluentBadge>@Loc[nameof(ControlsStrings.LabelNone)]</FluentBadge>
}
}
</ChildContent>
<MoreButtonTemplate Context="overflow">
@* Display must be inline block so the width is correctly calculated. *@
<span class="dimension-tag" style="display:inline-block;">
@($"+{overflow.ItemsOverflow.Count()}")
</span>
</MoreButtonTemplate>
<OverflowTemplate Context="overflow">
@* Intentionally empty. Don't display an overflow template here. *@
</OverflowTemplate>
</FluentOverflow>
</AspireTemplateColumn>
<AspireTemplateColumn>
@{
var id = $"typeFilterButton-{context.SanitizedHtmlId}-{Guid.NewGuid()}";
}
<FluentButton id="@id"
IconEnd="@(new Icons.Regular.Size20.Filter())"
Appearance="@(context.AreAllValuesSelected is true ? Appearance.Stealth : Appearance.Accent)"
@onclick="() => context.PopupVisible = !context.PopupVisible"
aria-label="@(context.AreAllValuesSelected is true ? Loc[nameof(ControlsStrings.ChartContainerAllTags)] : Loc[nameof(ControlsStrings.ChartContainerFilteredTags)])"/>
<FluentPopover AnchorId="@id" @bind-Open="context.PopupVisible" VerticalThreshold="200" AutoFocus="false">
<Header>@context.Name</Header>
<Body>
<FluentStack Orientation="Orientation.Vertical" Class="dimension-popup">
<FluentCheckbox Label="@Loc[nameof(ControlsStrings.LabelAll)]"
ThreeState="true"
ShowIndeterminate="false"
ThreeStateOrderUncheckToIntermediate="true"
@bind-CheckState="context.AreAllValuesSelected"/>
@foreach (var tag in context.Values.OrderBy(v => v.Text))
{
var isChecked = context.SelectedValues.Contains(tag);
<FluentCheckbox Label="@tag.Text"
title="@tag.Text"
@key=tag
@bind-Value:get="isChecked"
@bind-Value:set="c => context.OnTagSelectionChanged(tag, c)"/>
}
</FluentStack>
</Body>
</FluentPopover>
</AspireTemplateColumn>
</ChildContent>
</FluentDataGrid>
else
{
var i = 0;
foreach (var item in context.SelectedValues.OrderBy(v => v.Text))
{
// Always display the first item by setting a fixed value.
<FluentOverflowItem Fixed="@((i == 0) ? OverflowItemFixed.Ellipsis : OverflowItemFixed.None)">
<span class="dimension-tag">@item.Text</span>
</FluentOverflowItem>
i++;
}
}
</ChildContent>
<MoreButtonTemplate Context="overflow">
@* Display must be inline block so the width is correctly calculated. *@
<span class="dimension-tag" style="display:inline-block;">
@($"+{overflow.ItemsOverflow.Count()}")
</span>
</MoreButtonTemplate>
<OverflowTemplate Context="overflow">
@* Intentionally empty. Don't display an overflow template here. *@
</OverflowTemplate>
</FluentOverflow>
</AspireTemplateColumn>
<AspireTemplateColumn ColumnManager="@_manager" ColumnId="@FilterColumn">
@{
var id = $"typeFilterButton-{context.SanitizedHtmlId}-{Guid.NewGuid()}";
}
<FluentButton id="@id"
IconEnd="@(new Icons.Regular.Size20.Filter())"
Appearance="@(context.AreAllValuesSelected is true ? Appearance.Stealth : Appearance.Accent)"
@onclick="() => context.PopupVisible = !context.PopupVisible"
aria-label="@(context.AreAllValuesSelected is true ? Loc[nameof(ControlsStrings.ChartContainerAllTags)] : Loc[nameof(ControlsStrings.ChartContainerFilteredTags)])"/>
<FluentPopover AnchorId="@id" @bind-Open="context.PopupVisible" VerticalThreshold="200" AutoFocus="false" FixedPlacement="true">
<Header>@context.Name</Header>
<Body>
<FluentStack Orientation="Orientation.Vertical" Class="dimension-popup">
<FluentCheckbox Label="@Loc[nameof(ControlsStrings.LabelAll)]"
ThreeState="true"
ShowIndeterminate="false"
ThreeStateOrderUncheckToIntermediate="true"
@bind-CheckState="context.AreAllValuesSelected"/>
@foreach (var tag in context.Values.OrderBy(v => v.Text))
{
var isChecked = context.SelectedValues.Contains(tag);
<FluentCheckbox Label="@tag.Text"
title="@tag.Text"
@key=tag
@bind-Value:get="isChecked"
@bind-Value:set="c => context.OnTagSelectionChanged(tag, c)"/>
}
</FluentStack>
</Body>
</FluentPopover>
</AspireTemplateColumn>
</ChildContent>
</FluentDataGrid>
</GridColumnManager>
</div>
}
@if (Instrument.Summary.Type == OtlpInstrumentType.Histogram)
@if (InstrumentData.Summary.Type == OtlpInstrumentType.Histogram)
{
<div class="metrics-filters-section">
<h5>@Loc[nameof(ControlsStrings.ChartContainerOptionsHeader)]</h5>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,42 @@ namespace Aspire.Dashboard.Components;

public partial class ChartFilters
{
private const string NameColumn = nameof(NameColumn);
private const string DimensionColumn = nameof(DimensionColumn);
private const string FilterColumn = nameof(FilterColumn);

[Parameter, EditorRequired]
public required OtlpInstrumentData InstrumentData { get; init; }

[Parameter, EditorRequired]
public required OtlpInstrumentData Instrument { get; set; }
public required InstrumentViewModel InstrumentViewModel { get; init; }

[Parameter, EditorRequired]
public required InstrumentViewModel InstrumentViewModel { get; set; }
public required List<DimensionFilterViewModel> DimensionFilters { get; init; }

[Parameter, EditorRequired]
public required List<DimensionFilterViewModel> DimensionFilters { get; set; }
public required bool IsRenderedInsideModal { get; init; }

public bool ShowCounts { get; set; }

private GridColumnManager _manager = null!;
private IList<GridColumn> _gridColumns = null!;

protected override void OnInitialized()
{
_gridColumns = [
new GridColumn(Name: NameColumn, DesktopWidth: "200px", MobileWidth: "2fr"),
new GridColumn(Name: DimensionColumn, DesktopWidth: "1fr", MobileWidth: "1fr"),
new GridColumn(Name: FilterColumn, DesktopWidth: "auto", MobileWidth: "auto")
];

InstrumentViewModel.DataUpdateSubscriptions.Add(() =>
{
ShowCounts = InstrumentViewModel.ShowCount;
return Task.CompletedTask;
});

ShowCounts = InstrumentViewModel.ShowCount;
}

private void ShowCountChanged()
Expand Down
Loading
Loading