Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion src/ServiceControl.Persistence/CustomCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class CustomCheck : IVersionedRow
public DateTime ReportedAt { get; set; }
public string? FailureReason { get; set; }
public EndpointDetails? OriginatingEndpoint { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[
Id, CustomCheckId, Category, Status, ReportedAt, FailureReason,
OriginatingEndpoint?.Name, OriginatingEndpoint?.HostId, OriginatingEndpoint?.Host
Expand Down
2 changes: 1 addition & 1 deletion src/ServiceControl.Persistence/EndpointsView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class EndpointsView : IVersionedRow
public bool MonitorHeartbeat { get; set; }
public HeartbeatInformation? HeartbeatInformation { get; set; }
public bool IsSendingHeartbeats { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[
Id, Name, HostDisplayName, Monitored, MonitorHeartbeat, IsSendingHeartbeats,
HeartbeatInformation?.LastReportAt, HeartbeatInformation?.ReportedStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class EventLogItemView : IVersionedRow
public List<string> RelatedTo { get; set; } = [];
public required string Category { get; set; }
public required string EventType { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[Id, Description, Severity, RaisedAt, Category, EventType, .. RelatedTo];
}
}
2 changes: 1 addition & 1 deletion src/ServiceControl.Persistence/FailureGroupView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public class FailureGroupView : IVersionedRow
public string? Comment { get; set; }
public DateTime First { get; set; }
public DateTime Last { get; set; }
object?[] IVersionedRow.VersionFields => [Id, Title, Type, Count, Comment, First, Last];
object?[] IVersionedRow.GetVersionFields() => [Id, Title, Type, Count, Comment, First, Last];
}
}
2 changes: 1 addition & 1 deletion src/ServiceControl.Persistence/GroupOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class GroupOperation : IVersionedRow
public DateTime? OperationStartTime { get; set; }
public DateTime? OperationCompletionTime { get; set; }
public bool NeedUserAcknowledgement { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[
Id, Title, Type, Count, Comment, First, Last, OperationStatus, OperationFailed, OperationProgress,
OperationMessagesCompletedCount, OperationRemainingCount, OperationStartTime, OperationCompletionTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class HistoricRetryOperation : IVersionedRow
public string? Originator { get; set; }
public bool Failed { get; set; }
public int NumberOfMessagesProcessed { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[RequestId, RetryType, StartTime, CompletionTime, Originator, Failed, NumberOfMessagesProcessed];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class UnacknowledgedRetryOperation : IVersionedRow
public string? Classifier { get; set; }
public bool Failed { get; set; }
public int NumberOfMessagesProcessed { get; set; }
object?[] IVersionedRow.VersionFields =>
object?[] IVersionedRow.GetVersionFields() =>
[RequestId, RetryType, StartTime, CompletionTime, Last, Originator, Classifier, Failed, NumberOfMessagesProcessed];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static DataVersion OverRows<TRow>((string Name, object? Value)[]? summary

public static DataVersion OverRows<TRow>((string Name, object? Value)[]? summary, IEnumerable<TRow> rows)
where TRow : IVersionedRow =>
OverRows(summary, rows, row => row.VersionFields);
OverRows(summary, rows, row => row.GetVersionFields());

/// <summary>
/// One version for a result gathered from several instances. Missing anywhere means missing overall.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
namespace ServiceControl.Persistence.Infrastructure
{
/// <summary>
/// A row the API sends to ServicePulse to be rendered. Every field the response shows has to appear in <c>VersionFields</c>, or it
/// can change without the cache tag moving and the client keeps a stale page.
/// A row the API sends to ServicePulse to be rendered. Every field the response shows has to appear in
/// <c>GetVersionFields</c>, or it can change without the cache tag moving and the client keeps a stale page.
/// </summary>
public interface IVersionedRow
{
object?[] VersionFields { get; }
// A method, not a property: We dont want a serialiser to persist computed properties into the document, and these
// values are derived from the fields beside them.
object?[] GetVersionFields();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class MessageRedirect : IVersionedRow
public required string FromPhysicalAddress { get; set; }
public required string ToPhysicalAddress { get; set; }
public DateTime LastModified { get; set; }
object?[] IVersionedRow.VersionFields => [MessageRedirectId, ToPhysicalAddress, LastModified];
object?[] IVersionedRow.GetVersionFields() => [MessageRedirectId, ToPhysicalAddress, LastModified];
static ConcurrentDictionary<string, Guid> idCache = new ConcurrentDictionary<string, Guid>();
}
}
2 changes: 1 addition & 1 deletion src/ServiceControl.Persistence/QueueAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ public class QueueAddress : IVersionedRow
{
public string? PhysicalAddress { get; set; }
public int FailedMessageCount { get; set; }
object?[] IVersionedRow.VersionFields => [PhysicalAddress, FailedMessageCount];
object?[] IVersionedRow.GetVersionFields() => [PhysicalAddress, FailedMessageCount];
}
}
23 changes: 16 additions & 7 deletions src/ServiceControl.UnitTests/Infrastructure/VersionedRowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ namespace ServiceControl.UnitTests.Infrastructure
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using Raven.Client.Documents.Conventions;
using ServiceControl.EventLog;
using ServiceControl.Infrastructure.WebApi;
using ServiceControl.Operations;
using ServiceControl.Persistence;
using ServiceControl.Persistence.Infrastructure;
using JsonSerializer = System.Text.Json.JsonSerializer;
using Sparrow.Json;

[TestFixture]
public class VersionedRowTests
Expand All @@ -30,15 +31,19 @@ public void The_rows_are_found_at_all()
"the scan below found nothing, so every other test here would pass without checking anything");
}

// The declaration is implemented explicitly so the member stays off the type's public surface.
// Declared as an ordinary public property it would be serialised alongside the real fields.
// RavenDB serialises explicit interface implementations, under their fully qualified name, where
// System.Text.Json ignores them. Some of these rows are stored as-is, so a computed member lands in the document.
[TestCaseSource(nameof(RowTypes))]
public void The_covered_field_list_is_not_part_of_the_response(Type type)
public void Only_the_rows_own_fields_are_stored(Type type)
{
var row = Seeded(type);
var conventions = new DocumentConventions();
conventions.Serialization.Initialize(conventions);

using var context = JsonOperationContext.ShortTermSingleUse();
using var stored = conventions.Serialization.DefaultConverter.ToBlittable(Seeded(type), context);

Assert.That(JsonSerializer.Serialize(row, type, SerializerOptions.Default),
Does.Not.Contain("version_fields").IgnoreCase);
Assert.That(stored.GetPropertyNames(), Is.EquivalentTo(Stored(type)),
$"{type.Name} would be written to the database with something other than its own properties, so a value computed from the fields beside it gets stored next to them");
}

[TestCaseSource(nameof(RowTypes))]
Expand Down Expand Up @@ -75,6 +80,10 @@ public void Changing_any_rendered_field_moves_the_version(Type type)
static PropertyInfo[] Rendered(Type type) =>
[.. type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(property => property.CanWrite)];

// Read-only ones count: a serialiser stores them too, and they are legitimate data.
static string[] Stored(Type type) =>
[.. type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(property => property.Name)];

// Every field set to something, so a row is never checked with a null standing in for a real value
// and a computed property never has to cope with an unset one.
static IVersionedRow Seeded(Type type)
Expand Down