feat(deploy): surface connection-level changes in the deployment diff (#10)
ComputeConnectionsDiff existed with tests but was never called and ConfigurationDiff had no slot for it, so standalone connection endpoint/protocol/failover drift never appeared in the deployment diff (only per-attribute binding drift did). Add a ConnectionChanges slot, wire ComputeConnectionsDiff into ComputeDiff, and render the connection section in the deployment diff UI.
This commit is contained in:
@@ -864,12 +864,137 @@
|
||||
? "The deployed revision hash differs from the current template-derived hash. Redeploy to apply changes."
|
||||
: "No differences between deployed and current configuration.");
|
||||
builder.CloseElement();
|
||||
|
||||
// DeploymentManager-018: render the structured diff sections so
|
||||
// the operator sees WHAT changed, not just that the hash moved.
|
||||
// Each section uses the same compact change-table idiom; the
|
||||
// connection section surfaces standalone endpoint/protocol/
|
||||
// failover drift that no per-attribute row would show (#10).
|
||||
var d = diffResult.Diff;
|
||||
if (d != null)
|
||||
{
|
||||
RenderChangeSection(builder, 100_000, "Attributes", d.AttributeChanges,
|
||||
a => a.Value ?? "—");
|
||||
|
||||
RenderChangeSection(builder, 200_000, "Alarms", d.AlarmChanges,
|
||||
a => $"P{a.PriorityLevel} · {a.TriggerType}");
|
||||
|
||||
RenderChangeSection(builder, 300_000, "Scripts", d.ScriptChanges,
|
||||
s => s.TriggerType ?? "—");
|
||||
|
||||
RenderChangeSection(builder, 400_000, "Connections", d.ConnectionChanges,
|
||||
c => FormatConnection(c));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await _diffDialog.ShowAsync($"Deployment Diff — {inst.UniqueName}", body);
|
||||
}
|
||||
|
||||
// Compact summary of a connection's deployment-relevant fields for the diff
|
||||
// table's Before/After cells: protocol, primary endpoint config, and the
|
||||
// failover retry count. Mirrors the fields ConnectionsEqual compares.
|
||||
private static string FormatConnection(
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening.ConnectionConfig c)
|
||||
{
|
||||
var endpoint = string.IsNullOrWhiteSpace(c.ConfigurationJson) ? "—" : c.ConfigurationJson;
|
||||
return $"{c.Protocol} · {endpoint} · failover ×{c.FailoverRetryCount}";
|
||||
}
|
||||
|
||||
// Renders one change section (a heading plus a Bootstrap change-table) for a
|
||||
// set of diff entries, matching the deployment-diff idiom used elsewhere in
|
||||
// the UI: table-sm/table-striped, a colored change badge, and Before/After
|
||||
// text columns. Nothing is rendered when the section has no entries, so the
|
||||
// four sections (attributes, alarms, scripts, connections) all read the same
|
||||
// and only appear when they actually changed. seqBase values are spaced
|
||||
// 100k apart so each section's per-row sequence numbers (13 per row) stay in
|
||||
// a disjoint, ascending range no matter how many entries a section has.
|
||||
private static void RenderChangeSection<T>(
|
||||
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder,
|
||||
int seqBase,
|
||||
string heading,
|
||||
IReadOnlyList<ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening.DiffEntry<T>> entries,
|
||||
Func<T, string> summarize)
|
||||
{
|
||||
if (entries.Count == 0)
|
||||
return;
|
||||
|
||||
builder.OpenElement(seqBase, "div");
|
||||
builder.AddAttribute(seqBase + 1, "class", "mt-3");
|
||||
|
||||
builder.OpenElement(seqBase + 2, "div");
|
||||
builder.AddAttribute(seqBase + 3, "class", "fw-semibold small mb-1");
|
||||
builder.AddContent(seqBase + 4, $"{heading} ({entries.Count})");
|
||||
builder.CloseElement();
|
||||
|
||||
builder.OpenElement(seqBase + 5, "table");
|
||||
builder.AddAttribute(seqBase + 6, "class", "table table-sm table-striped align-middle mb-0");
|
||||
|
||||
// Header row.
|
||||
builder.OpenElement(seqBase + 7, "thead");
|
||||
builder.OpenElement(seqBase + 8, "tr");
|
||||
AppendHeaderCell(builder, seqBase + 9, "Name");
|
||||
AppendHeaderCell(builder, seqBase + 12, "Change");
|
||||
AppendHeaderCell(builder, seqBase + 15, "Before");
|
||||
AppendHeaderCell(builder, seqBase + 18, "After");
|
||||
builder.CloseElement(); // tr
|
||||
builder.CloseElement(); // thead
|
||||
|
||||
builder.OpenElement(seqBase + 21, "tbody");
|
||||
var rowSeq = seqBase + 22;
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
builder.OpenElement(rowSeq, "tr");
|
||||
|
||||
builder.OpenElement(rowSeq + 1, "td");
|
||||
builder.AddContent(rowSeq + 2, entry.CanonicalName);
|
||||
builder.CloseElement();
|
||||
|
||||
builder.OpenElement(rowSeq + 3, "td");
|
||||
builder.OpenElement(rowSeq + 4, "span");
|
||||
builder.AddAttribute(rowSeq + 5, "class", ChangeBadgeClass(entry.ChangeType));
|
||||
builder.AddContent(rowSeq + 6, entry.ChangeType.ToString());
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
|
||||
builder.OpenElement(rowSeq + 7, "td");
|
||||
builder.AddAttribute(rowSeq + 8, "class", "small text-muted");
|
||||
builder.AddContent(rowSeq + 9,
|
||||
entry.OldValue is null ? "—" : summarize(entry.OldValue));
|
||||
builder.CloseElement();
|
||||
|
||||
builder.OpenElement(rowSeq + 10, "td");
|
||||
builder.AddAttribute(rowSeq + 11, "class", "small");
|
||||
builder.AddContent(rowSeq + 12,
|
||||
entry.NewValue is null ? "—" : summarize(entry.NewValue));
|
||||
builder.CloseElement();
|
||||
|
||||
builder.CloseElement(); // tr
|
||||
rowSeq += 13;
|
||||
}
|
||||
builder.CloseElement(); // tbody
|
||||
|
||||
builder.CloseElement(); // table
|
||||
builder.CloseElement(); // div.mt-3
|
||||
}
|
||||
|
||||
private static void AppendHeaderCell(
|
||||
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder, int seq, string text)
|
||||
{
|
||||
builder.OpenElement(seq, "th");
|
||||
builder.AddAttribute(seq + 1, "scope", "col");
|
||||
builder.AddContent(seq + 2, text);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
private static string ChangeBadgeClass(
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening.DiffChangeType changeType) => changeType switch
|
||||
{
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening.DiffChangeType.Added => "badge bg-success",
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening.DiffChangeType.Removed => "badge bg-danger",
|
||||
_ => "badge bg-warning text-dark",
|
||||
};
|
||||
|
||||
// ---- Dropdown option helpers ----
|
||||
private IEnumerable<(int Id, string Label)> EnumerateSiteOptions()
|
||||
{
|
||||
|
||||
@@ -12,8 +12,8 @@ public sealed record ConfigurationDiff
|
||||
public string? OldRevisionHash { get; init; }
|
||||
/// <summary>Revision hash of the new configuration being compared.</summary>
|
||||
public string? NewRevisionHash { get; init; }
|
||||
/// <summary>True when any attribute, alarm, or script changes are present.</summary>
|
||||
public bool HasChanges => AttributeChanges.Count > 0 || AlarmChanges.Count > 0 || ScriptChanges.Count > 0;
|
||||
/// <summary>True when any attribute, alarm, script, or connection changes are present.</summary>
|
||||
public bool HasChanges => AttributeChanges.Count > 0 || AlarmChanges.Count > 0 || ScriptChanges.Count > 0 || ConnectionChanges.Count > 0;
|
||||
|
||||
/// <summary>Diff entries for resolved attributes.</summary>
|
||||
public IReadOnlyList<DiffEntry<ResolvedAttribute>> AttributeChanges { get; init; } = [];
|
||||
@@ -21,6 +21,13 @@ public sealed record ConfigurationDiff
|
||||
public IReadOnlyList<DiffEntry<ResolvedAlarm>> AlarmChanges { get; init; } = [];
|
||||
/// <summary>Diff entries for resolved scripts.</summary>
|
||||
public IReadOnlyList<DiffEntry<ResolvedScript>> ScriptChanges { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Diff entries for connection configurations, keyed by connection name.
|
||||
/// Surfaces standalone endpoint/protocol/failover drift that does not show
|
||||
/// up as a per-attribute binding change (TemplateEngine-018).
|
||||
/// </summary>
|
||||
public IReadOnlyList<DiffEntry<ConnectionConfig>> ConnectionChanges { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,6 +42,13 @@ public class DiffService
|
||||
s => s.CanonicalName,
|
||||
ScriptsEqual);
|
||||
|
||||
// TemplateEngine-018: surface standalone connection endpoint/protocol/
|
||||
// failover drift. Per-attribute binding changes already show up under
|
||||
// AttributeChanges, but a connection's own ConfigurationJson /
|
||||
// BackupConfigurationJson / Protocol / FailoverRetryCount edits do not —
|
||||
// those only appear here.
|
||||
var connectionChanges = ComputeConnectionsDiff(oldConfig, newConfig);
|
||||
|
||||
return new ConfigurationDiff
|
||||
{
|
||||
InstanceUniqueName = newConfig.InstanceUniqueName,
|
||||
@@ -49,7 +56,8 @@ public class DiffService
|
||||
NewRevisionHash = newRevisionHash,
|
||||
AttributeChanges = attributeChanges,
|
||||
AlarmChanges = alarmChanges,
|
||||
ScriptChanges = scriptChanges
|
||||
ScriptChanges = scriptChanges,
|
||||
ConnectionChanges = connectionChanges
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,11 +167,10 @@ public class DiffService
|
||||
/// TemplateEngine-018: produces a per-connection diff between two flattened
|
||||
/// configurations, emitting Added / Removed / Changed entries keyed by the
|
||||
/// connection name. Mirrors the existing <see cref="ComputeEntityDiff{T}"/>
|
||||
/// shape used for attributes / alarms / scripts but is exposed as a separate
|
||||
/// method because <see cref="ConfigurationDiff"/> in
|
||||
/// <c>ZB.MOM.WW.ScadaBridge.Commons</c> does not yet carry a <c>ConnectionChanges</c>
|
||||
/// slot — the public diff record will be extended in a paired Commons change
|
||||
/// (this file is the only one in this fix's scope). A null
|
||||
/// shape used for attributes / alarms / scripts. Called by
|
||||
/// <see cref="ComputeDiff"/> to populate
|
||||
/// <see cref="ConfigurationDiff.ConnectionChanges"/>, and exposed publicly so
|
||||
/// callers can compute connection drift in isolation. A null
|
||||
/// <c>Connections</c> dictionary on either side is treated as the empty map.
|
||||
/// </summary>
|
||||
/// <param name="oldConfig">The previously deployed configuration, or null
|
||||
|
||||
Reference in New Issue
Block a user