feat(ui): MES reportable checkbox on the template alarm dialog + MES column on the Alarms tab
Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
This commit is contained in:
+16
-2
@@ -41,6 +41,13 @@
|
|||||||
<label class="form-check-label" for="alarmLocked">Locked</label>
|
<label class="form-check-label" for="alarmLocked">Locked</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" @bind="_isMesReportable" id="alarmMesReportable" />
|
||||||
|
<label class="form-check-label" for="alarmMesReportable">MES reportable</label>
|
||||||
|
<div class="form-text">Returned by the MES alarm-status API as <code>IsFlaggedForMES</code>. Instances can override this unless the alarm is locked.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@if (_error != null)
|
@if (_error != null)
|
||||||
{
|
{
|
||||||
<div class="col-12"><div class="text-danger small" data-test="alarm-form-error">@_error</div></div>
|
<div class="col-12"><div class="text-danger small" data-test="alarm-form-error">@_error</div></div>
|
||||||
@@ -67,6 +74,9 @@
|
|||||||
[Parameter] public string? InitialTriggerConfig { get; set; }
|
[Parameter] public string? InitialTriggerConfig { get; set; }
|
||||||
[Parameter] public bool InitialIsLocked { get; set; }
|
[Parameter] public bool InitialIsLocked { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Seeds the "MES reportable" flag returned by the MES alarm-status API.</summary>
|
||||||
|
[Parameter] public bool InitialIsMesReportable { get; set; }
|
||||||
|
|
||||||
/// <summary>Attribute choices offered by the structured trigger editor's picker.</summary>
|
/// <summary>Attribute choices offered by the structured trigger editor's picker.</summary>
|
||||||
[Parameter] public IReadOnlyList<AlarmAttributeChoice> AvailableAttributes { get; set; }
|
[Parameter] public IReadOnlyList<AlarmAttributeChoice> AvailableAttributes { get; set; }
|
||||||
= Array.Empty<AlarmAttributeChoice>();
|
= Array.Empty<AlarmAttributeChoice>();
|
||||||
@@ -82,6 +92,7 @@
|
|||||||
private AlarmTriggerType _triggerType;
|
private AlarmTriggerType _triggerType;
|
||||||
private string? _triggerConfig;
|
private string? _triggerConfig;
|
||||||
private bool _isLocked;
|
private bool _isLocked;
|
||||||
|
private bool _isMesReportable;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
|
|
||||||
@@ -92,6 +103,7 @@
|
|||||||
_triggerType = InitialTriggerType;
|
_triggerType = InitialTriggerType;
|
||||||
_triggerConfig = InitialTriggerConfig;
|
_triggerConfig = InitialTriggerConfig;
|
||||||
_isLocked = InitialIsLocked;
|
_isLocked = InitialIsLocked;
|
||||||
|
_isMesReportable = InitialIsMesReportable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Submit()
|
private async Task Submit()
|
||||||
@@ -103,7 +115,7 @@
|
|||||||
_busy = true;
|
_busy = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var error = await OnSaveAsync(new AlarmDraft(_name, _triggerType, _priority, _triggerConfig, _isLocked));
|
var error = await OnSaveAsync(new AlarmDraft(_name, _triggerType, _priority, _triggerConfig, _isLocked, _isMesReportable));
|
||||||
if (error == null)
|
if (error == null)
|
||||||
{
|
{
|
||||||
Context.Close(true);
|
Context.Close(true);
|
||||||
@@ -125,10 +137,12 @@
|
|||||||
/// <param name="Priority">Priority level.</param>
|
/// <param name="Priority">Priority level.</param>
|
||||||
/// <param name="TriggerConfig">Structured trigger configuration payload.</param>
|
/// <param name="TriggerConfig">Structured trigger configuration payload.</param>
|
||||||
/// <param name="IsLocked">Whether instances may override the alarm.</param>
|
/// <param name="IsLocked">Whether instances may override the alarm.</param>
|
||||||
|
/// <param name="IsMesReportable">Whether the MES alarm-status API reports this alarm.</param>
|
||||||
public sealed record AlarmDraft(
|
public sealed record AlarmDraft(
|
||||||
string Name,
|
string Name,
|
||||||
AlarmTriggerType TriggerType,
|
AlarmTriggerType TriggerType,
|
||||||
int Priority,
|
int Priority,
|
||||||
string? TriggerConfig,
|
string? TriggerConfig,
|
||||||
bool IsLocked);
|
bool IsLocked,
|
||||||
|
bool IsMesReportable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -907,6 +907,7 @@
|
|||||||
<th>Trigger</th>
|
<th>Trigger</th>
|
||||||
<th>Priority</th>
|
<th>Priority</th>
|
||||||
<th>Config</th>
|
<th>Config</th>
|
||||||
|
<th>MES</th>
|
||||||
<th>Lock</th>
|
<th>Lock</th>
|
||||||
<th style="width: 60px;">Actions</th>
|
<th style="width: 60px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -922,6 +923,16 @@
|
|||||||
config readable instead of merely hidden. *@
|
config readable instead of merely hidden. *@
|
||||||
<td class="small text-muted text-truncate" style="max-width: 200px;"
|
<td class="small text-muted text-truncate" style="max-width: 200px;"
|
||||||
title="@alarm.TriggerConfiguration">@(alarm.TriggerConfiguration ?? "—")</td>
|
title="@alarm.TriggerConfiguration">@(alarm.TriggerConfiguration ?? "—")</td>
|
||||||
|
<td data-test="alarm-mes-badge">
|
||||||
|
@if (alarm.IsMesReportable)
|
||||||
|
{
|
||||||
|
<span class="badge bg-success" aria-label="MES reportable">MES</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="badge bg-light text-dark" aria-label="Not MES reportable">—</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (alarm.IsLocked)
|
@if (alarm.IsLocked)
|
||||||
{
|
{
|
||||||
@@ -1436,7 +1447,7 @@
|
|||||||
private Task OpenAddAlarmDialog()
|
private Task OpenAddAlarmDialog()
|
||||||
=> ShowAlarmDialogAsync(
|
=> ShowAlarmDialogAsync(
|
||||||
editId: null,
|
editId: null,
|
||||||
new TemplateAlarmDialog.AlarmDraft(string.Empty, default, 500, null, false));
|
new TemplateAlarmDialog.AlarmDraft(string.Empty, default, 500, null, false, false));
|
||||||
|
|
||||||
private Task OpenEditAlarmDialog(TemplateAlarm alarm)
|
private Task OpenEditAlarmDialog(TemplateAlarm alarm)
|
||||||
=> ShowAlarmDialogAsync(
|
=> ShowAlarmDialogAsync(
|
||||||
@@ -1446,7 +1457,8 @@
|
|||||||
alarm.TriggerType,
|
alarm.TriggerType,
|
||||||
alarm.PriorityLevel,
|
alarm.PriorityLevel,
|
||||||
alarm.TriggerConfiguration,
|
alarm.TriggerConfiguration,
|
||||||
alarm.IsLocked));
|
alarm.IsLocked,
|
||||||
|
alarm.IsMesReportable));
|
||||||
|
|
||||||
private async Task ShowAlarmDialogAsync(int? editId, TemplateAlarmDialog.AlarmDraft initial)
|
private async Task ShowAlarmDialogAsync(int? editId, TemplateAlarmDialog.AlarmDraft initial)
|
||||||
{
|
{
|
||||||
@@ -1461,6 +1473,7 @@
|
|||||||
InitialPriority="initial.Priority"
|
InitialPriority="initial.Priority"
|
||||||
InitialTriggerConfig="@initial.TriggerConfig"
|
InitialTriggerConfig="@initial.TriggerConfig"
|
||||||
InitialIsLocked="initial.IsLocked"
|
InitialIsLocked="initial.IsLocked"
|
||||||
|
InitialIsMesReportable="initial.IsMesReportable"
|
||||||
AvailableAttributes="choices"
|
AvailableAttributes="choices"
|
||||||
OnSaveAsync="d => SaveAlarmDraftAsync(editId, d)" />,
|
OnSaveAsync="d => SaveAlarmDraftAsync(editId, d)" />,
|
||||||
size: "modal-dialog-scrollable");
|
size: "modal-dialog-scrollable");
|
||||||
@@ -1521,6 +1534,7 @@
|
|||||||
PriorityLevel = draft.Priority,
|
PriorityLevel = draft.Priority,
|
||||||
TriggerConfiguration = draft.TriggerConfig?.Trim(),
|
TriggerConfiguration = draft.TriggerConfig?.Trim(),
|
||||||
IsLocked = draft.IsLocked,
|
IsLocked = draft.IsLocked,
|
||||||
|
IsMesReportable = draft.IsMesReportable,
|
||||||
Description = existing.Description,
|
Description = existing.Description,
|
||||||
OnTriggerScriptId = existing.OnTriggerScriptId,
|
OnTriggerScriptId = existing.OnTriggerScriptId,
|
||||||
};
|
};
|
||||||
@@ -1536,7 +1550,8 @@
|
|||||||
TriggerType = draft.TriggerType,
|
TriggerType = draft.TriggerType,
|
||||||
PriorityLevel = draft.Priority,
|
PriorityLevel = draft.Priority,
|
||||||
TriggerConfiguration = draft.TriggerConfig?.Trim(),
|
TriggerConfiguration = draft.TriggerConfig?.Trim(),
|
||||||
IsLocked = draft.IsLocked
|
IsLocked = draft.IsLocked,
|
||||||
|
IsMesReportable = draft.IsMesReportable
|
||||||
};
|
};
|
||||||
|
|
||||||
var addResult = await TemplateService.AddAlarmAsync(_selectedTemplate.Id, alarm, user);
|
var addResult = await TemplateService.AddAlarmAsync(_selectedTemplate.Id, alarm, user);
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The template alarm authoring surface exposes <c>TemplateAlarm.IsMesReportable</c>:
|
||||||
|
/// an "MES reportable" checkbox in the dialog body and an MES column on the
|
||||||
|
/// template's Alarms tab. <c>TemplateEdit</c> is a heavyweight page (~10 injected
|
||||||
|
/// services with their own graphs), so — like the rest of the TemplateEdit coverage
|
||||||
|
/// in this suite (see <c>TemplateNativeAlarmSourceEditorTests</c>) — these are
|
||||||
|
/// structural assertions over the component source that pin the wiring so it cannot
|
||||||
|
/// silently regress. The persistence behaviour itself is covered by the
|
||||||
|
/// TemplateService / ManagementActor alarm tests.
|
||||||
|
/// </summary>
|
||||||
|
public class TemplateAlarmDialogMesReportableTests
|
||||||
|
{
|
||||||
|
private static string DesignComponentsRoot
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dir = AppContext.BaseDirectory;
|
||||||
|
for (var i = 0; i < 6 && dir is not null; i++)
|
||||||
|
dir = Directory.GetParent(dir)?.FullName;
|
||||||
|
return Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI",
|
||||||
|
"Components", "Pages", "Design");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string AlarmDialogMarkup
|
||||||
|
=> File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateAlarmDialog.razor"));
|
||||||
|
|
||||||
|
private static string TemplateEditMarkup
|
||||||
|
=> File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateEdit.razor"));
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AlarmDialog_HasMesReportableCheckbox()
|
||||||
|
{
|
||||||
|
var markup = AlarmDialogMarkup;
|
||||||
|
Assert.Contains("@bind=\"_isMesReportable\"", markup);
|
||||||
|
Assert.Contains("id=\"alarmMesReportable\"", markup);
|
||||||
|
Assert.Contains("MES reportable", markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AlarmDialog_ThreadsMesReportableThroughParameterAndDraft()
|
||||||
|
{
|
||||||
|
var markup = AlarmDialogMarkup;
|
||||||
|
Assert.Contains("InitialIsMesReportable", markup);
|
||||||
|
Assert.Contains("_isMesReportable = InitialIsMesReportable", markup);
|
||||||
|
Assert.Contains("bool IsMesReportable", markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AlarmsTab_HasMesColumn()
|
||||||
|
{
|
||||||
|
var markup = TemplateEditMarkup;
|
||||||
|
Assert.Contains("<th>MES</th>", markup);
|
||||||
|
Assert.Contains("data-test=\"alarm-mes-badge\"", markup);
|
||||||
|
Assert.Contains("alarm.IsMesReportable", markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AlarmsTab_PassesMesReportableIntoAndOutOfTheDialog()
|
||||||
|
{
|
||||||
|
var markup = TemplateEditMarkup;
|
||||||
|
Assert.Contains("InitialIsMesReportable=\"initial.IsMesReportable\"", markup);
|
||||||
|
Assert.Contains("IsMesReportable = draft.IsMesReportable", markup);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user