Skip to content

Commit

Permalink
More changes to make dates and times consistent across dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Feb 19, 2024
1 parent b3dda71 commit c743cc2
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 29 deletions.
16 changes: 14 additions & 2 deletions src/Aspire.Dashboard/Components/Controls/PlotlyChart.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Model.MetricValues;
using Aspire.Dashboard.Resources;
using Aspire.Dashboard.Utils;
using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
Expand Down Expand Up @@ -173,7 +174,7 @@ private sealed class Trace

private string FormatTooltip(string name, double yValue, DateTime xValue)
{
return $"<b>{InstrumentViewModel.Instrument?.Name}</b><br />{name}: {yValue.ToString("##,0.######", CultureInfo.InvariantCulture)}<br />Time: {xValue.ToString("h:mm:ss tt", CultureInfo.InvariantCulture)}";
return $"<b>{InstrumentViewModel.Instrument?.Name}</b><br />{name}: {yValue.ToString("##,0.######", CultureInfo.CurrentCulture)}<br />Time: {FormatHelpers.FormatTime(xValue)}";
}

private static HistogramValue GetHistogramValue(MetricValueBase metric)
Expand Down Expand Up @@ -431,12 +432,23 @@ private async Task UpdateChart(bool tickUpdate, DateTime inProgressDataTime)

if (!tickUpdate)
{
// The chart mostly shows numbers but some localization is needed for displaying time ticks.
var is24Hour = DateTimeFormatInfo.CurrentInfo.LongTimePattern.StartsWith("H", StringComparison.Ordinal);
// Plotly uses d3-time-format https://d3js.org/d3-time-format
var time = is24Hour ? "%H:%M:%S" : "%-I:%M:%S %p";
var userLocale = new
{
periods = new string[] { DateTimeFormatInfo.CurrentInfo.AMDesignator, DateTimeFormatInfo.CurrentInfo.PMDesignator },
time = time
};

await JSRuntime.InvokeVoidAsync("initializeChart",
"plotly-chart-container",
traceDtos,
xValues,
inProgressDataTime.ToLocalTime(),
(inProgressDataTime - Duration).ToLocalTime()).ConfigureAwait(false);
(inProgressDataTime - Duration).ToLocalTime(),
userLocale).ConfigureAwait(false);
}
else
{
Expand Down
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@using Aspire.Dashboard.Components.ResourcesGridColumns
@using Aspire.Dashboard.Model
@using Aspire.Dashboard.Resources
@using Aspire.Dashboard.Utils
@inject IStringLocalizer<Dashboard.Resources.Resources> Loc
@inject IStringLocalizer<ControlsStrings> ControlsStringsLoc

Expand Down Expand Up @@ -57,7 +58,7 @@
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort">
<StateColumnDisplay Resource="@context" UnviewedErrorCounts ="@_applicationUnviewedErrorCounts" />
</TemplateColumn>
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStartTimeColumnHeader)]" Sortable="true" SortBy="@_startTimeSort" Tooltip="true">
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStartTimeColumnHeader)]" Sortable="true" SortBy="@_startTimeSort" TooltipText="@(context => context.CreationTimeStamp != null ? FormatHelpers.FormatDateTime(context.CreationTimeStamp.Value, false) : null)" Tooltip="true">
<StartTimeColumnDisplay Resource="@context" />
</TemplateColumn>
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesSourceColumnHeader)]">
Expand Down
4 changes: 3 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsLevelColumnHeader)]">
<LogLevelColumnDisplay LogEntry="@context" />
</TemplateColumn>
<PropertyColumn Title="@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsTimestampColumnHeader)]" Property="@(context => FormatHelpers.FormatTimeStamp(context.TimeStamp))" Tooltip="true" />
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsTimestampColumnHeader)]" TooltipText="@(context => FormatHelpers.FormatDateTime(context.TimeStamp, true))" Tooltip="true">
@FormatHelpers.FormatTimeWithOptionalDate(context.TimeStamp, includeMilliseconds: true)
</TemplateColumn>
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsMessageColumnHeader)]" Tooltip="true" TooltipText="(e) => e.Message">
<LogMessageColumnDisplay FilterText="@(ViewModel.FilterText)" LogEntry="@context" />
</TemplateColumn>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>
<FluentToolbar Orientation="Orientation.Horizontal">
<div>
@Loc[nameof(Dashboard.Resources.TraceDetail.TraceDetailTraceStartHeader)] <strong>@_trace.FirstSpan.StartTime.ToString("MMMM d yyyy"), @FormatHelpers.FormatTimeStamp(_trace.FirstSpan.StartTime)</strong>
@Loc[nameof(Dashboard.Resources.TraceDetail.TraceDetailTraceStartHeader)] <strong>@FormatHelpers.FormatDateTime(_trace.FirstSpan.StartTime, includeMilliseconds: true)</strong>
</div>
<FluentDivider Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
<div>
Expand Down
4 changes: 3 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Traces.razor
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
<div class="datagrid-overflow-area continuous-scroll-overflow" tabindex="-1">
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="OtlpTrace" GridTemplateColumns="0.8fr 2fr 3fr 0.8fr 0.5fr">
<ChildContent>
<PropertyColumn Title="@ControlsStringsLoc[nameof(ControlsStrings.TimestampColumnHeader)]" Property="@(context => FormatHelpers.FormatTimeStamp(context.FirstSpan.StartTime))" />
<TemplateColumn Title="@ControlsStringsLoc[nameof(ControlsStrings.TimestampColumnHeader)]" TooltipText="@(context => FormatHelpers.FormatDateTime(context.FirstSpan.StartTime, true))" Tooltip="true">
@FormatHelpers.FormatTimeWithOptionalDate(context.FirstSpan.StartTime, includeMilliseconds: true)
</TemplateColumn>
<TemplateColumn Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Tooltip="true" TooltipText="@((t) => $"{t.FullName}: {OtlpHelpers.ToShortenedId(t.TraceId)}")">
<span><FluentHighlighter HighlightedText="@(TracesViewModel.FilterText)" Text="@(context.FullName)" /></span>
<span class="trace-id">@OtlpHelpers.ToShortenedId(context.TraceId)</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,8 @@

@if (Resource.CreationTimeStamp is {} timeStamp)
{
if (timeStamp.ToLocalTime().Date == DateTime.Now.Date)
{
// e.g. "08:57:44" (based on user's culture and preferences)
// Don't include milliseconds as resource server returned time stamp is second precision.
@FormatHelpers.FormatTime(timeStamp)
}
else
{
// e.g. "9/02/2024 08:57:44" (based on user's culture and preferences)
@FormatHelpers.FormatDateTime(timeStamp)
}
// Don't include milliseconds as resource server returned time stamp is second precision.
@FormatHelpers.FormatTimeWithOptionalDate(timeStamp)
}

@code {
Expand Down
40 changes: 31 additions & 9 deletions src/Aspire.Dashboard/Utils/FormatHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ static string GetLongTimePatternWithMilliseconds()
var longTimePattern = DateTimeFormatInfo.CurrentInfo.LongTimePattern;

// Create a format similar to .fff but based on the current culture.
// Intentionally use fff here instead of FFF so output has a consistent length.
var millisecondFormat = $"{NumberFormatInfo.CurrentInfo.NumberDecimalSeparator}fff";

// Append millisecond pattern to current culture's long time pattern.
Expand All @@ -26,22 +27,43 @@ static string GetLongTimePatternWithMilliseconds()
private static partial Regex MatchSecondsInTimeFormatPattern();

private static readonly string s_longTimePatternWithMilliseconds = GetLongTimePatternWithMilliseconds();
private static readonly string s_shortDateLongTimePatternWithMilliseconds = DateTimeFormatInfo.CurrentInfo.ShortDatePattern + " " + GetLongTimePatternWithMilliseconds();

public static string FormatTimeStamp(DateTime time)
public static string FormatTime(DateTime value, bool includeMilliseconds = false)
{
// Long time with milliseconds
return time.ToLocalTime().ToString(s_longTimePatternWithMilliseconds, CultureInfo.CurrentCulture);
}
var local = value.ToLocalTime();

public static string FormatTime(DateTime time)
{
// Long time
return time.ToLocalTime().ToString("T", CultureInfo.CurrentCulture);
return includeMilliseconds
? local.ToString(s_longTimePatternWithMilliseconds, CultureInfo.CurrentCulture)
: local.ToString("T", CultureInfo.CurrentCulture);
}

public static string FormatDateTime(DateTime dateTime)
public static string FormatDateTime(DateTime value, bool includeMilliseconds = false)
{
var local = value.ToLocalTime();

// Short date, long time
return dateTime.ToLocalTime().ToString("G", CultureInfo.CurrentCulture);
return includeMilliseconds
? local.ToString(s_shortDateLongTimePatternWithMilliseconds, CultureInfo.CurrentCulture)
: local.ToString("G", CultureInfo.CurrentCulture);
}

public static string FormatTimeWithOptionalDate(DateTime value, bool includeMilliseconds = false)
{
var local = value.ToLocalTime();

// If the date is today then only return time, otherwise return entire date time text.
if (local.Date == DateTime.Now.Date)
{
// e.g. "08:57:44" (based on user's culture and preferences)
// Don't include milliseconds as resource server returned time stamp is second precision.
return FormatTime(local, includeMilliseconds);
}
else
{
// e.g. "9/02/2024 08:57:44" (based on user's culture and preferences)
return FormatDateTime(local, includeMilliseconds);
}
}
}
43 changes: 40 additions & 3 deletions src/Aspire.Dashboard/wwwroot/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ window.updateChart = function (id, traces, xValues, rangeStartTime, rangeEndTime
type: 'date',
range: [rangeEndTime, rangeStartTime],
fixedrange: true,
tickformat: "%-I:%M:%S %p",
tickformat: "%X",
color: themeColors.textColor
}
};
Expand All @@ -159,7 +159,9 @@ window.updateChart = function (id, traces, xValues, rangeStartTime, rangeEndTime
fixTraceLineRendering(chartDiv);
};

window.initializeChart = function (id, traces, xValues, rangeStartTime, rangeEndTime) {
window.initializeChart = function (id, traces, xValues, rangeStartTime, rangeEndTime, serverLocale) {
registerLocale(serverLocale);

var chartContainerDiv = document.getElementById(id);

// Reusing a div can create issues with chart lines appearing beyond the end range.
Expand Down Expand Up @@ -191,7 +193,7 @@ window.initializeChart = function (id, traces, xValues, rangeStartTime, rangeEnd
type: 'date',
range: [rangeEndTime, rangeStartTime],
fixedrange: true,
tickformat: "%-I:%M:%S %p",
tickformat: "%X",
color: themeColors.textColor
},
yaxis: {
Expand All @@ -218,3 +220,38 @@ window.initializeChart = function (id, traces, xValues, rangeStartTime, rangeEnd

fixTraceLineRendering(chartDiv);
};

function registerLocale(serverLocale) {
// Register the locale for Plotly.js. This is to enable localization of time format shown by the charts.
// Changing plotly.js time formatting is better than supplying values from the server which is very difficult to do correctly.

// Right now necessary changes are to:
// -Update AM/PM
// -Update time format to 12/24 hour.
var locale = {
moduleType: 'locale',
name: 'en',
dictionary: {
'Click to enter Colorscale title': 'Click to enter Colourscale title'
},
format: {
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
periods: serverLocale.periods,
dateTime: '%a %b %e %X %Y',
date: '%d/%m/%Y',
time: serverLocale.time,
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
}
};
Plotly.register(locale);
}

0 comments on commit c743cc2

Please sign in to comment.