feat(instance): native-alarm-source-override CSV bulk import (deferred #12) + doc fixes
Closes the operator parity gap the deferred-work register tracked as #12: native alarm source overrides could only be set one-at-a-time, while attribute overrides had a CSV bulk path. Adds an all-or-nothing CSV import for native sources. - Commons: extract the RFC-4180 line splitter into a shared CsvLineSplitter (refactor OverrideCsvParser onto it — no behavior change, pinned by its tests); new NativeAlarmSourceOverrideCsvParser (header SourceName,Connection, SourceReference,Filter; blank = inherited). - Commons: NativeAlarmSourceOverrideEntry + bulk SetInstanceNativeAlarmSource- OverridesCommand (auto-registered via the reflection command registry). - ManagementService: Deployer-gated handler — flattens once, validates every source resolves + is unlocked + no duplicates up front, then upserts the whole batch under a single SaveChanges (true all-or-nothing txn). Added to the frozen authorization matrix; dispatch-coverage guard passes. - CLI: `instance native-alarm-source import --instance-id --file` (parity with `instance import-overrides`) + README. - Tests: native parser (Commons), CLI parse/entry mapping, 3 bulk-handler tests (happy path single-commit, locked-source reject, unresolved-source reject). Also corrects a stale XML-doc line in ScriptRuntimeContext (WaitForAttribute quality-gated mode is shipped, not "planned") and updates the deferred-work register: marks #7/#13/#15/#16/#20 verified-resolved, #12 as CLI/API-shipped with only the Central UI upload affordance still pending. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -477,6 +477,53 @@ public static class InstanceCommands
|
||||
});
|
||||
group.Add(clearCmd);
|
||||
|
||||
// import (CSV bulk apply — parity with `instance import-overrides`)
|
||||
var importIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||
var importFileOption = new Option<string>("--file")
|
||||
{
|
||||
Description = "Path to the override CSV file (columns: SourceName,Connection,SourceReference,Filter; blank field = inherited)",
|
||||
Required = true
|
||||
};
|
||||
var importCmd = new Command("import")
|
||||
{
|
||||
Description = "Apply native alarm source overrides from a CSV file (all-or-nothing)"
|
||||
};
|
||||
importCmd.Add(importIdOption);
|
||||
importCmd.Add(importFileOption);
|
||||
importCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(importIdOption);
|
||||
var filePath = result.GetValue(importFileOption)!;
|
||||
|
||||
string csvText;
|
||||
try
|
||||
{
|
||||
csvText = File.ReadAllText(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OutputFormatter.WriteError($"Cannot read file '{filePath}': {ex.Message}", "FILE_READ_ERROR");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var parseResult = NativeAlarmSourceOverrideCsvParser.Parse(csvText);
|
||||
if (parseResult.Errors.Count > 0)
|
||||
{
|
||||
foreach (var error in parseResult.Errors)
|
||||
OutputFormatter.WriteError(error, "INVALID_CSV");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var overrides = parseResult.Rows
|
||||
.Select(r => new NativeAlarmSourceOverrideEntry(
|
||||
r.SourceName, r.Connection, r.SourceReference, r.Filter))
|
||||
.ToList();
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SetInstanceNativeAlarmSourceOverridesCommand(id, overrides));
|
||||
});
|
||||
group.Add(importCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
@@ -503,6 +503,31 @@ Clear an instance's native alarm source override, reverting to the inherited bin
|
||||
scadabridge --url <url> instance native-alarm-source clear --instance-id <int> --source <string>
|
||||
```
|
||||
|
||||
#### `instance native-alarm-source import`
|
||||
|
||||
Bulk-apply native alarm source overrides for a single instance from a CSV file
|
||||
(all-or-nothing — the native-source parity of `instance import-overrides`). The CSV
|
||||
header is required: `SourceName,Connection,SourceReference,Filter`; a blank override
|
||||
field keeps the inherited value. Every named source must resolve for the instance and
|
||||
be unlocked, or the whole batch is rejected before any write.
|
||||
|
||||
```sh
|
||||
scadabridge --url <url> instance native-alarm-source import --instance-id <int> --file <path>
|
||||
```
|
||||
|
||||
| Option | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `--instance-id` | yes | Instance ID |
|
||||
| `--file` | yes | Path to the override CSV (`SourceName,Connection,SourceReference,Filter`; blank field = inherited) |
|
||||
|
||||
Example CSV:
|
||||
|
||||
```csv
|
||||
SourceName,Connection,SourceReference,Filter
|
||||
Pressure,OpcB,ns=2;s=Pump.Alarm,Active
|
||||
Module.Temp,,,
|
||||
```
|
||||
|
||||
#### `instance deploy`
|
||||
|
||||
Deploy an instance to its site. Acquires the per-instance operation lock.
|
||||
|
||||
Reference in New Issue
Block a user