feat(central-ui): add Min time between runs field to the script form
The template script editor had no input for MinTimeBetweenRuns, so a WhileTrue trigger configured through the UI always saved a null interval and degraded to a single edge fire. The Add/Edit Script modal now has a "Min time between runs" number+unit (ms/sec/min) field. - Visible only for ValueChange / Conditional / Expression triggers — the auto-firing triggers MinTimeBetweenRuns throttles. Hidden for Interval (its own period is the cadence), Call (invoked explicitly, never throttled), and None. - For a WhileTrue Conditional/Expression trigger the field is labelled as the re-fire interval and shows a warning while it is blank. - Wired through the new-script and edit-script save paths (edit previously only preserved the existing value, never let the user change it). New DurationInput helper does the TimeSpan <-> number+unit conversion; ScriptTriggerConfigCodec.SupportsMinTimeBetweenRuns classifies trigger types. Both TDD'd — 21 new tests. CentralUI suite 316 green; verified end-to-end in the browser (visibility per trigger type, WhileTrue warning, save/reload round-trip).
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace ScadaLink.CentralUI.Components.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="TimeSpan"/> to and from the number+unit pair behind a
|
||||
/// duration input (milliseconds / seconds / minutes). A blank or non-positive
|
||||
/// number represents "unset" (a <c>null</c> duration).
|
||||
/// </summary>
|
||||
internal static class DurationInput
|
||||
{
|
||||
/// <summary>The unit tokens a duration input offers, smallest first.</summary>
|
||||
internal static readonly string[] Units = { "ms", "sec", "min" };
|
||||
|
||||
/// <summary>
|
||||
/// Splits a duration into the largest whole unit that represents it exactly.
|
||||
/// A null or non-positive duration yields a blank value and the default
|
||||
/// <c>sec</c> unit.
|
||||
/// </summary>
|
||||
internal static (string? Value, string Unit) Split(TimeSpan? duration)
|
||||
{
|
||||
if (duration is not { } d || d <= TimeSpan.Zero) return (null, "sec");
|
||||
|
||||
var ms = (long)d.TotalMilliseconds;
|
||||
if (ms % 60000 == 0) return ((ms / 60000).ToString(CultureInfo.InvariantCulture), "min");
|
||||
if (ms % 1000 == 0) return ((ms / 1000).ToString(CultureInfo.InvariantCulture), "sec");
|
||||
return (ms.ToString(CultureInfo.InvariantCulture), "ms");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composes a number+unit pair into a duration. A blank, unparseable, or
|
||||
/// non-positive value yields <c>null</c> (unset).
|
||||
/// </summary>
|
||||
internal static TimeSpan? Compose(string? value, string unit)
|
||||
{
|
||||
if (!long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n)
|
||||
|| n <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var factorMs = unit switch
|
||||
{
|
||||
"min" => 60000L,
|
||||
"ms" => 1L,
|
||||
_ => 1000L,
|
||||
};
|
||||
return TimeSpan.FromMilliseconds(n * factorMs);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,17 @@ internal static class ScriptTriggerConfigCodec
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a trigger type honours the script's <c>MinTimeBetweenRuns</c>.
|
||||
/// True for the auto-firing triggers it throttles — ValueChange, Conditional,
|
||||
/// Expression. False for Interval (its own period is the cadence), Call
|
||||
/// (invoked explicitly, never throttled), and None/Unknown.
|
||||
/// </summary>
|
||||
internal static bool SupportsMinTimeBetweenRuns(string? triggerType) =>
|
||||
ParseKind(triggerType) is ScriptTriggerKind.ValueChange
|
||||
or ScriptTriggerKind.Conditional
|
||||
or ScriptTriggerKind.Expression;
|
||||
|
||||
/// <summary>Canonical <c>TriggerType</c> string for a kind; null for None/Unknown.</summary>
|
||||
internal static string? KindToString(ScriptTriggerKind kind) => kind switch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user