feat(cli): typed setpoint flags for template alarm add (serializes trigger-config JSON)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI;
|
||||
|
||||
/// <summary>
|
||||
/// Serializes typed alarm-setpoint CLI flags into the trigger-config JSON the
|
||||
/// server expects. Key names MUST stay in lockstep with the canonical codec at
|
||||
/// src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs
|
||||
/// (that codec is internal to CentralUI, so this is a deliberate CLI-side mirror;
|
||||
/// the round-trip test verifies the JSON against the live server — the real contract).
|
||||
/// </summary>
|
||||
internal static class AlarmTriggerConfigJson
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the trigger-config JSON for <paramref name="triggerType"/> from the typed
|
||||
/// flags, or returns null when none are supplied (so the alarm is created without a
|
||||
/// trigger config). Unknown/blank trigger types yield null.
|
||||
/// </summary>
|
||||
internal static string? Build(
|
||||
string triggerType, string? attribute,
|
||||
string? matchValue, bool notEquals,
|
||||
double? min, double? max,
|
||||
double? thresholdPerSecond, double? windowSeconds, string? direction,
|
||||
double? loLo, double? lo, double? hi, double? hiHi,
|
||||
string? expression)
|
||||
{
|
||||
var type = triggerType?.Trim();
|
||||
var anyTyped = attribute is not null || matchValue is not null || notEquals
|
||||
|| min.HasValue || max.HasValue || thresholdPerSecond.HasValue || windowSeconds.HasValue
|
||||
|| direction is not null || loLo.HasValue || lo.HasValue || hi.HasValue || hiHi.HasValue
|
||||
|| expression is not null;
|
||||
if (!anyTyped) return null;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using (var w = new Utf8JsonWriter(stream))
|
||||
{
|
||||
w.WriteStartObject();
|
||||
if (!string.Equals(type, "Expression", StringComparison.OrdinalIgnoreCase))
|
||||
w.WriteString("attributeName", attribute ?? "");
|
||||
|
||||
switch (type?.ToLowerInvariant())
|
||||
{
|
||||
case "valuematch":
|
||||
var mv = matchValue ?? "";
|
||||
if (notEquals) mv = "!=" + mv;
|
||||
w.WriteString("matchValue", mv);
|
||||
break;
|
||||
case "rangeviolation":
|
||||
if (min.HasValue) w.WriteNumber("min", min.Value);
|
||||
if (max.HasValue) w.WriteNumber("max", max.Value);
|
||||
break;
|
||||
case "rateofchange":
|
||||
if (thresholdPerSecond.HasValue) w.WriteNumber("thresholdPerSecond", thresholdPerSecond.Value);
|
||||
if (windowSeconds.HasValue) w.WriteNumber("windowSeconds", windowSeconds.Value);
|
||||
w.WriteString("direction", NormalizeDirection(direction));
|
||||
break;
|
||||
case "hilo":
|
||||
if (loLo.HasValue) w.WriteNumber("loLo", loLo.Value);
|
||||
if (lo.HasValue) w.WriteNumber("lo", lo.Value);
|
||||
if (hi.HasValue) w.WriteNumber("hi", hi.Value);
|
||||
if (hiHi.HasValue) w.WriteNumber("hiHi", hiHi.Value);
|
||||
break;
|
||||
case "expression":
|
||||
w.WriteString("expression", expression ?? "");
|
||||
break;
|
||||
}
|
||||
w.WriteEndObject();
|
||||
}
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
|
||||
// Mirrors AlarmTriggerConfigCodec.NormalizeDirection.
|
||||
private static string NormalizeDirection(string? raw) => raw?.ToLowerInvariant() switch
|
||||
{
|
||||
"rising" or "up" or "positive" => "rising",
|
||||
"falling" or "down" or "negative" => "falling",
|
||||
_ => "either",
|
||||
};
|
||||
}
|
||||
@@ -226,6 +226,22 @@ public static class TemplateCommands
|
||||
var lockedOption = new Option<bool>("--locked") { Description = "Lock status" };
|
||||
lockedOption.DefaultValueFactory = _ => false;
|
||||
|
||||
// Typed setpoint flags (alternative to raw --trigger-config; raw wins when both supplied).
|
||||
var attributeOption = new Option<string?>("--attribute") { Description = "Attribute name the trigger watches (all trigger types except Expression)" };
|
||||
var matchValueOption = new Option<string?>("--match-value") { Description = "ValueMatch: value to compare against" };
|
||||
var notEqualsOption = new Option<bool>("--not-equals") { Description = "ValueMatch: match when the value is NOT equal (emits !=)" };
|
||||
notEqualsOption.DefaultValueFactory = _ => false;
|
||||
var minOption = new Option<double?>("--min") { Description = "RangeViolation: minimum allowed value" };
|
||||
var maxOption = new Option<double?>("--max") { Description = "RangeViolation: maximum allowed value" };
|
||||
var thresholdOption = new Option<double?>("--threshold-per-second") { Description = "RateOfChange: rate threshold per second" };
|
||||
var windowOption = new Option<double?>("--window-seconds") { Description = "RateOfChange: sliding window in seconds" };
|
||||
var directionOption = new Option<string?>("--direction") { Description = "RateOfChange: direction (rising|falling|either)" };
|
||||
var loLoOption = new Option<double?>("--lolo") { Description = "HiLo: low-low setpoint" };
|
||||
var loOption = new Option<double?>("--lo") { Description = "HiLo: low setpoint" };
|
||||
var hiOption = new Option<double?>("--hi") { Description = "HiLo: high setpoint" };
|
||||
var hiHiOption = new Option<double?>("--hihi") { Description = "HiLo: high-high setpoint" };
|
||||
var expressionOption = new Option<string?>("--expression") { Description = "Expression: boolean trigger expression" };
|
||||
|
||||
var addCmd = new Command("add") { Description = "Add an alarm to a template" };
|
||||
addCmd.Add(templateIdOption);
|
||||
addCmd.Add(nameOption);
|
||||
@@ -234,17 +250,41 @@ public static class TemplateCommands
|
||||
addCmd.Add(descOption);
|
||||
addCmd.Add(triggerConfigOption);
|
||||
addCmd.Add(lockedOption);
|
||||
addCmd.Add(attributeOption);
|
||||
addCmd.Add(matchValueOption);
|
||||
addCmd.Add(notEqualsOption);
|
||||
addCmd.Add(minOption);
|
||||
addCmd.Add(maxOption);
|
||||
addCmd.Add(thresholdOption);
|
||||
addCmd.Add(windowOption);
|
||||
addCmd.Add(directionOption);
|
||||
addCmd.Add(loLoOption);
|
||||
addCmd.Add(loOption);
|
||||
addCmd.Add(hiOption);
|
||||
addCmd.Add(hiHiOption);
|
||||
addCmd.Add(expressionOption);
|
||||
addCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var triggerType = result.GetValue(triggerTypeOption)!;
|
||||
var rawConfig = result.GetValue(triggerConfigOption);
|
||||
var triggerConfig = rawConfig ?? AlarmTriggerConfigJson.Build(
|
||||
triggerType,
|
||||
result.GetValue(attributeOption),
|
||||
result.GetValue(matchValueOption), result.GetValue(notEqualsOption),
|
||||
result.GetValue(minOption), result.GetValue(maxOption),
|
||||
result.GetValue(thresholdOption), result.GetValue(windowOption), result.GetValue(directionOption),
|
||||
result.GetValue(loLoOption), result.GetValue(loOption), result.GetValue(hiOption), result.GetValue(hiHiOption),
|
||||
result.GetValue(expressionOption));
|
||||
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new AddTemplateAlarmCommand(
|
||||
result.GetValue(templateIdOption),
|
||||
result.GetValue(nameOption)!,
|
||||
result.GetValue(triggerTypeOption)!,
|
||||
triggerType,
|
||||
result.GetValue(priorityOption)!,
|
||||
result.GetValue(descOption),
|
||||
result.GetValue(triggerConfigOption),
|
||||
triggerConfig,
|
||||
result.GetValue(lockedOption)));
|
||||
});
|
||||
group.Add(addCmd);
|
||||
|
||||
Reference in New Issue
Block a user