feat(ui): rich AlarmTriggerEditor in instance override modal
Replaces the per-row JSON textbox with an Edit button that opens a modal hosting the full AlarmTriggerEditor. The editor pre-populates with the merged inherited + override config so the operator sees the effective state, not the override delta. On Save: - HiLo: diff against inherited, store only changed keys - Binary trigger types: whole-replace if the edited config differs Value comparison in the diff is type-aware (decoded strings, numeric GetDouble) so JSON-escape differences (e.g., literal em-dash vs —) don't produce false-positive diffs that pollute the override JSON. FlatteningService.MergeHiLoConfig is now public so the UI can pre-merge the editor seed; new public DiffHiLoConfig handles the symmetric direction. +2 encoding tests cover the new equivalence behavior. The override row's summary column shows the diff'd keys + priority chip so operators see what's overridden at a glance.
This commit is contained in:
@@ -407,7 +407,7 @@ public class FlatteningService
|
||||
/// Returns the derived config verbatim on parse failure of either input —
|
||||
/// the existing whole-replace behavior is the safe fallback.
|
||||
/// </summary>
|
||||
internal static string? MergeHiLoConfig(string? inheritedJson, string? derivedJson)
|
||||
public static string? MergeHiLoConfig(string? inheritedJson, string? derivedJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(inheritedJson)) return derivedJson;
|
||||
if (string.IsNullOrWhiteSpace(derivedJson)) return inheritedJson;
|
||||
@@ -455,6 +455,83 @@ public class FlatteningService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the minimal HiLo override JSON given the inherited config and
|
||||
/// an edited config — returns only the top-level keys whose values differ
|
||||
/// from the inherited config. Returns <c>null</c> when no keys differ (the
|
||||
/// caller should treat that as "no override").
|
||||
///
|
||||
/// Value comparison is type-aware so that JSON-escape differences (e.g.,
|
||||
/// a literal em-dash in the inherited config vs. <c>—</c> in the
|
||||
/// editor's serialized output) don't produce false-positive diffs. On
|
||||
/// parse failure of either input, returns <paramref name="editedJson"/>
|
||||
/// verbatim — safe fallback that matches the existing whole-replace
|
||||
/// semantics.
|
||||
/// </summary>
|
||||
public static string? DiffHiLoConfig(string? inheritedJson, string? editedJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editedJson)) return null;
|
||||
if (string.IsNullOrWhiteSpace(inheritedJson)) return editedJson;
|
||||
|
||||
try
|
||||
{
|
||||
using var inheritedDoc = JsonDocument.Parse(inheritedJson);
|
||||
using var editedDoc = JsonDocument.Parse(editedJson);
|
||||
|
||||
if (inheritedDoc.RootElement.ValueKind != JsonValueKind.Object
|
||||
|| editedDoc.RootElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return editedJson;
|
||||
}
|
||||
|
||||
var changed = new List<JsonProperty>();
|
||||
foreach (var prop in editedDoc.RootElement.EnumerateObject())
|
||||
{
|
||||
if (!inheritedDoc.RootElement.TryGetProperty(prop.Name, out var inhProp))
|
||||
{
|
||||
changed.Add(prop);
|
||||
continue;
|
||||
}
|
||||
if (!ValuesEquivalent(prop.Value, inhProp))
|
||||
changed.Add(prop);
|
||||
}
|
||||
|
||||
if (changed.Count == 0) return null;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new Utf8JsonWriter(stream))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
foreach (var p in changed) p.WriteTo(writer);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
return System.Text.Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return editedJson;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two JSON values by their decoded meaning rather than their
|
||||
/// raw text. Strings are unescaped before comparison so equivalent values
|
||||
/// in different escape forms (e.g., a literal "—" vs. "—") match.
|
||||
/// Numbers compare by their double value so trailing-zero differences
|
||||
/// don't produce false diffs.
|
||||
/// </summary>
|
||||
private static bool ValuesEquivalent(JsonElement a, JsonElement b)
|
||||
{
|
||||
if (a.ValueKind != b.ValueKind) return false;
|
||||
return a.ValueKind switch
|
||||
{
|
||||
JsonValueKind.String => a.GetString() == b.GetString(),
|
||||
JsonValueKind.Number => a.GetDouble() == b.GetDouble(),
|
||||
JsonValueKind.True or JsonValueKind.False or JsonValueKind.Null => true,
|
||||
_ => a.GetRawText() == b.GetRawText()
|
||||
};
|
||||
}
|
||||
|
||||
private static void ResolveComposedAlarms(
|
||||
IReadOnlyList<Template> templateChain,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<TemplateComposition>> compositionMap,
|
||||
|
||||
Reference in New Issue
Block a user