diff --git a/src/MxGateway.Server/Dashboard/DashboardMxValueFormatter.cs b/src/MxGateway.Server/Dashboard/DashboardMxValueFormatter.cs index 545fdea..1fde4c1 100644 --- a/src/MxGateway.Server/Dashboard/DashboardMxValueFormatter.cs +++ b/src/MxGateway.Server/Dashboard/DashboardMxValueFormatter.cs @@ -10,6 +10,9 @@ namespace MxGateway.Server.Dashboard; /// public static class DashboardMxValueFormatter { + /// Maximum array elements rendered inline before the value is truncated. + private const int MaxArrayElements = 24; + /// Formats the value payload of an . /// The value to format; may be null. /// A display string — never null. @@ -37,7 +40,7 @@ public static class DashboardMxValueFormatter .ToDateTimeOffset() .UtcDateTime .ToString("yyyy-MM-dd HH:mm:ss.fff 'UTC'", CultureInfo.InvariantCulture), - MxValue.KindOneofCase.ArrayValue => "(array)", + MxValue.KindOneofCase.ArrayValue => FormatArray(value.ArrayValue), MxValue.KindOneofCase.RawValue => $"({value.RawValue.Length} bytes)", _ => "-", }; @@ -45,9 +48,63 @@ public static class DashboardMxValueFormatter /// Formats the MXAccess data type of an . /// The value whose data type to describe; may be null. - /// The data-type name — never null. + /// The data-type name — never null. Arrays render as Element[dims]. public static string FormatDataType(MxValue? value) { - return value is null ? "-" : value.DataType.ToString(); + if (value is null) + { + return "-"; + } + + // A scalar carries its type in MxValue.DataType, but an array leaves + // that Unspecified and carries the element type on the MxArray itself. + return value.KindCase == MxValue.KindOneofCase.ArrayValue + ? FormatArrayDataType(value.ArrayValue) + : value.DataType.ToString(); + } + + private static string FormatArrayDataType(MxArray array) + { + string dimensions = array.Dimensions.Count > 0 + ? string.Join(",", array.Dimensions) + : string.Empty; + return $"{array.ElementDataType}[{dimensions}]"; + } + + private static string FormatArray(MxArray array) + { + IReadOnlyList elements = array.ValuesCase switch + { + MxArray.ValuesOneofCase.BoolValues => + array.BoolValues.Values.Select(item => item ? "true" : "false").ToArray(), + MxArray.ValuesOneofCase.Int32Values => + array.Int32Values.Values.Select(item => item.ToString(CultureInfo.InvariantCulture)).ToArray(), + MxArray.ValuesOneofCase.Int64Values => + array.Int64Values.Values.Select(item => item.ToString(CultureInfo.InvariantCulture)).ToArray(), + MxArray.ValuesOneofCase.FloatValues => + array.FloatValues.Values.Select(item => item.ToString("G7", CultureInfo.InvariantCulture)).ToArray(), + MxArray.ValuesOneofCase.DoubleValues => + array.DoubleValues.Values.Select(item => item.ToString("G15", CultureInfo.InvariantCulture)).ToArray(), + MxArray.ValuesOneofCase.StringValues => + array.StringValues.Values.Select(item => $"\"{item}\"").ToArray(), + MxArray.ValuesOneofCase.TimestampValues => + array.TimestampValues.Values + .Select(item => item.ToDateTimeOffset().UtcDateTime + .ToString("yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture)) + .ToArray(), + MxArray.ValuesOneofCase.RawValues => + array.RawValues.Values.Select(item => $"({item.Length} bytes)").ToArray(), + _ => [], + }; + + if (elements.Count == 0) + { + return "[]"; + } + + string body = string.Join(", ", elements.Take(MaxArrayElements)); + return elements.Count > MaxArrayElements + ? $"[{body}, … {elements.Count} total]" + : $"[{body}]"; } } diff --git a/src/MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs b/src/MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs index e226f10..88aa7ff 100644 --- a/src/MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs +++ b/src/MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs @@ -126,4 +126,17 @@ public sealed class DashboardBrowseAndAlarmModelTests Assert.True(unackedRow.IsUnacknowledged); Assert.False(ackedRow.IsUnacknowledged); } + + [Fact] + public void FormatValue_AndDataType_RenderArrayElementsAndElementType() + { + MxArray array = new() { ElementDataType = MxDataType.Double }; + array.Dimensions.Add(3u); + array.DoubleValues = new DoubleArray(); + array.DoubleValues.Values.Add(new[] { 1.5, 2.25, 3.0 }); + MxValue value = new() { ArrayValue = array }; + + Assert.Equal("[1.5, 2.25, 3]", DashboardMxValueFormatter.FormatValue(value)); + Assert.Equal("Double[3]", DashboardMxValueFormatter.FormatDataType(value)); + } }