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:
Joseph Doherty
2026-05-18 16:44:15 -04:00
parent 437fe154e7
commit 01509a045f
5 changed files with 241 additions and 2 deletions

View File

@@ -98,6 +98,8 @@
private string _scriptCode = string.Empty;
private string? _scriptTriggerType;
private string? _scriptTriggerConfig;
private string? _scriptMinTimeValue;
private string _scriptMinTimeUnit = "sec";
private string? _scriptParameters;
private string? _scriptReturn;
private bool _scriptIsLocked;
@@ -880,6 +882,47 @@
Changed="@OnScriptTriggerChanged"
AvailableAttributes="@BuildAlarmAttributeChoices()" />
</div>
@if (ScriptTriggerConfigCodec.SupportsMinTimeBetweenRuns(_scriptTriggerType))
{
<div class="col-12">
<label class="form-label">Min time between runs</label>
<div class="row g-2" style="max-width: 420px;">
<div class="col-7">
<input type="number" min="1" step="1" class="form-control"
placeholder="(optional)"
@bind="_scriptMinTimeValue" @bind:event="oninput" />
</div>
<div class="col-5">
<select class="form-select" @bind="_scriptMinTimeUnit">
<option value="ms">milliseconds</option>
<option value="sec">seconds</option>
<option value="min">minutes</option>
</select>
</div>
</div>
@if (ScriptTriggerIsWhileTrue())
{
<div class="form-text">
This is the re-fire interval for the
<strong>WhileTrue</strong> trigger above.
</div>
@if (DurationInput.Compose(_scriptMinTimeValue, _scriptMinTimeUnit) is null)
{
<div class="alert alert-warning py-1 px-2 small mt-1 mb-0">
The WhileTrue trigger has no interval set — the script
will fire only once. Set a value here to make it re-fire.
</div>
}
}
else
{
<div class="form-text">
Optional throttle — skips trigger invocations that fire
sooner than this.
</div>
}
</div>
}
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="_scriptIsLocked" id="scriptLocked" />
@@ -1461,6 +1504,19 @@
_scriptTriggerConfig = v.Config;
}
/// <summary>
/// True when the current script trigger is a WhileTrue Conditional/Expression
/// trigger — the case where the "Min time between runs" interval is required
/// (it is the re-fire cadence).
/// </summary>
private bool ScriptTriggerIsWhileTrue()
{
var kind = ScriptTriggerConfigCodec.ParseKind(_scriptTriggerType);
return kind is ScriptTriggerKind.Conditional or ScriptTriggerKind.Expression
&& ScriptTriggerConfigCodec.Parse(_scriptTriggerConfig, kind).Mode
== ScriptTriggerMode.WhileTrue;
}
private void BeginAddScript()
{
_showScriptForm = true;
@@ -1470,6 +1526,7 @@
_scriptCode = string.Empty;
_scriptTriggerType = null;
_scriptTriggerConfig = null;
(_scriptMinTimeValue, _scriptMinTimeUnit) = DurationInput.Split(null);
_scriptParameters = null;
_scriptReturn = null;
_scriptIsLocked = false;
@@ -1486,6 +1543,7 @@
_scriptCode = script.Code;
_scriptTriggerType = script.TriggerType;
_scriptTriggerConfig = script.TriggerConfiguration;
(_scriptMinTimeValue, _scriptMinTimeUnit) = DurationInput.Split(script.MinTimeBetweenRuns);
_scriptParameters = script.ParameterDefinitions;
_scriptReturn = script.ReturnDefinition;
_scriptIsLocked = script.IsLocked;
@@ -1581,7 +1639,7 @@
ParameterDefinitions = _scriptParameters,
ReturnDefinition = _scriptReturn,
IsLocked = _scriptIsLocked,
MinTimeBetweenRuns = existing.MinTimeBetweenRuns,
MinTimeBetweenRuns = DurationInput.Compose(_scriptMinTimeValue, _scriptMinTimeUnit),
IsInherited = existing.IsInherited,
LockedInDerived = existing.LockedInDerived,
};
@@ -1606,7 +1664,8 @@
TriggerConfiguration = _scriptTriggerConfig?.Trim(),
ParameterDefinitions = _scriptParameters,
ReturnDefinition = _scriptReturn,
IsLocked = _scriptIsLocked
IsLocked = _scriptIsLocked,
MinTimeBetweenRuns = DurationInput.Compose(_scriptMinTimeValue, _scriptMinTimeUnit)
};
var addResult = await TemplateService.AddScriptAsync(_selectedTemplate.Id, script, user);