feat(scripts): realign Test Run with runtime API, add anonymous-object calls and instance binding
The Test Run sandbox and Monaco analysis modelled a script API that had drifted from the site runtime's ScriptGlobals, so real scripts failed to compile in Test Run. Realign both to the runtime surface (Instance/Scripts/ExternalSystem/Attributes/Children/Parent) and drop the duplicate ScriptHost stub so the two cannot diverge again. - Script calls (Scripts.CallShared, Instance.CallScript, Route.To().Call) accept an anonymous object instead of a hand-built dictionary, via a shared ScriptArgs normalizer; existing dictionary calls still compile. - Test Run can optionally bind to a deployed instance, so Instance/ Attributes/CallScript route to it cross-site; adds site-side RouteToGetAttributes/RouteToSetAttributes handlers. - Adds Test Run panels to the API method and template script editors. - Fixes the TestDatabaseQuery seed script, which queried a table that never existed. Also commits unrelated in-progress work already in the tree: the health monitoring report loop, site streaming changes, and the Admin/Design data-connection and SMTP page reorganization.
This commit is contained in:
@@ -4,6 +4,7 @@ using ScadaLink.Commons.Messages.Artifacts;
|
||||
using ScadaLink.Commons.Messages.DebugView;
|
||||
using ScadaLink.Commons.Messages.Deployment;
|
||||
using ScadaLink.Commons.Messages.InboundApi;
|
||||
using ScadaLink.Commons.Messages.Instance;
|
||||
using ScadaLink.Commons.Messages.Lifecycle;
|
||||
using ScadaLink.Commons.Messages.ScriptExecution;
|
||||
using ScadaLink.Commons.Types.Enums;
|
||||
@@ -81,6 +82,8 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
|
||||
// Inbound API Route.To().Call() — route to Instance Actors
|
||||
Receive<RouteToCallRequest>(RouteInboundApiCall);
|
||||
Receive<RouteToGetAttributesRequest>(RouteInboundApiGetAttributes);
|
||||
Receive<RouteToSetAttributesRequest>(RouteInboundApiSetAttributes);
|
||||
|
||||
// Internal startup messages
|
||||
Receive<StartupConfigsLoaded>(HandleStartupConfigsLoaded);
|
||||
@@ -567,6 +570,75 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads attribute values from a deployed instance for a Route.To().GetAttribute(s)
|
||||
/// call (or a central Test Run bound to the instance). Asks the Instance Actor
|
||||
/// per attribute and combines the results.
|
||||
/// </summary>
|
||||
private void RouteInboundApiGetAttributes(RouteToGetAttributesRequest request)
|
||||
{
|
||||
if (!_instanceActors.TryGetValue(request.InstanceUniqueName, out var instanceActor))
|
||||
{
|
||||
Sender.Tell(new RouteToGetAttributesResponse(
|
||||
request.CorrelationId, new Dictionary<string, object?>(), false,
|
||||
$"Instance '{request.InstanceUniqueName}' not found on this site.",
|
||||
DateTimeOffset.UtcNow));
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = Sender;
|
||||
var names = request.AttributeNames;
|
||||
var asks = names
|
||||
.Select(name => instanceActor.Ask<GetAttributeResponse>(
|
||||
new GetAttributeRequest(
|
||||
request.CorrelationId, request.InstanceUniqueName, name, DateTimeOffset.UtcNow),
|
||||
TimeSpan.FromSeconds(30)))
|
||||
.ToArray();
|
||||
|
||||
Task.WhenAll(asks).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
{
|
||||
var values = new Dictionary<string, object?>();
|
||||
for (var i = 0; i < names.Count; i++)
|
||||
values[names[i]] = t.Result[i].Found ? t.Result[i].Value : null;
|
||||
return new RouteToGetAttributesResponse(
|
||||
request.CorrelationId, values, true, null, DateTimeOffset.UtcNow);
|
||||
}
|
||||
return new RouteToGetAttributesResponse(
|
||||
request.CorrelationId, new Dictionary<string, object?>(), false,
|
||||
t.Exception?.GetBaseException().Message ?? "Attribute read timed out",
|
||||
DateTimeOffset.UtcNow);
|
||||
}).PipeTo(sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes attribute values on a deployed instance for a Route.To().SetAttribute(s)
|
||||
/// call (or a central Test Run bound to the instance). Writes are Tell'd to the
|
||||
/// Instance Actor — serialized through its mailbox — and acknowledged optimistically,
|
||||
/// matching the fire-and-forget semantics of Instance.SetAttribute.
|
||||
/// </summary>
|
||||
private void RouteInboundApiSetAttributes(RouteToSetAttributesRequest request)
|
||||
{
|
||||
if (!_instanceActors.TryGetValue(request.InstanceUniqueName, out var instanceActor))
|
||||
{
|
||||
Sender.Tell(new RouteToSetAttributesResponse(
|
||||
request.CorrelationId, false,
|
||||
$"Instance '{request.InstanceUniqueName}' not found on this site.",
|
||||
DateTimeOffset.UtcNow));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (name, value) in request.AttributeValues)
|
||||
{
|
||||
instanceActor.Tell(new SetStaticAttributeCommand(
|
||||
request.CorrelationId, request.InstanceUniqueName, name, value, DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
Sender.Tell(new RouteToSetAttributesResponse(
|
||||
request.CorrelationId, true, null, DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-33: Handles system-wide artifact deployment (shared scripts, external systems, etc.).
|
||||
/// Persists artifacts to SiteStorageService and recompiles shared scripts.
|
||||
|
||||
@@ -216,26 +216,19 @@ public class InstanceActor : ReceiveActor
|
||||
PublishAndNotifyChildren(changed);
|
||||
|
||||
// Persist asynchronously -- fire and forget since the actor is the source of truth
|
||||
var self = Self;
|
||||
var sender = Sender;
|
||||
// and SetAttribute is called from scripts via Tell (no response consumer).
|
||||
var instanceName = _instanceUniqueName;
|
||||
var attributeName = command.AttributeName;
|
||||
var logger = _logger;
|
||||
_storage.SetStaticOverrideAsync(_instanceUniqueName, command.AttributeName, command.Value)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
var success = t.IsCompletedSuccessfully;
|
||||
var error = t.Exception?.GetBaseException().Message;
|
||||
if (!success)
|
||||
{
|
||||
// Value is already in memory; log the persistence failure
|
||||
// In-memory state is authoritative
|
||||
}
|
||||
return new SetStaticAttributeResponse(
|
||||
command.CorrelationId,
|
||||
_instanceUniqueName,
|
||||
command.AttributeName,
|
||||
success,
|
||||
error,
|
||||
DateTimeOffset.UtcNow);
|
||||
}).PipeTo(sender);
|
||||
logger.LogWarning(
|
||||
t.Exception?.GetBaseException(),
|
||||
"Failed to persist static override for {Instance}.{Attribute}; in-memory state is authoritative",
|
||||
instanceName,
|
||||
attributeName);
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user