Add event-driven quick action dispatch
This commit is contained in:
159
F4SD-ActionConnector/Bus/ActionBus.cs
Normal file
159
F4SD-ActionConnector/Bus/ActionBus.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using F4SD.ActionConnector.Enumerations;
|
||||
using F4SD.ActionConnector.Events;
|
||||
using F4SD.ActionConnector.Execution;
|
||||
using F4SD.ActionConnector.Models;
|
||||
using F4SD.ActionConnector.Payloads;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
|
||||
namespace F4SD.ActionConnector.Bus
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry point for the ActionConnector.
|
||||
/// Subscribes to <see cref="ActionConnectorEvents"/>, reads the configuration once,
|
||||
/// and dispatches actions whenever a trigger event fires.
|
||||
///
|
||||
/// Usage from the host application:
|
||||
/// <code>
|
||||
/// var bus = new ActionBus(new ActionBusOptions
|
||||
/// {
|
||||
/// ConfigurationFilePath = @"C:\Config\F4SD-ExternalCommunication-Configuration.xml",
|
||||
/// QuickActionExecutor = async request =>
|
||||
/// {
|
||||
/// await myJiraClient.CreateTicketAsync(request.QuickActionRef, request.Parameters);
|
||||
/// return ActionResult.Ok(request.ActionId);
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// bus.Start();
|
||||
/// // later:
|
||||
/// bus.Stop();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public sealed class ActionBus : IDisposable
|
||||
{
|
||||
private readonly ActionBusOptions _options;
|
||||
private readonly ActionDispatcher _dispatcher;
|
||||
private ExternalCommunicationConfiguration _configuration;
|
||||
private bool _running;
|
||||
|
||||
public ActionBus(ActionBusOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_dispatcher = new ActionDispatcher(options.QuickActionExecutor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the configuration and subscribes to all trigger events.
|
||||
/// Call once during application startup.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
var CM = MethodBase.GetCurrentMethod();
|
||||
LogMethodBegin(CM);
|
||||
try
|
||||
{
|
||||
_configuration = new ExternalCommunicationConfiguration();
|
||||
_configuration.Initiate(_options.ConfigRootElement, _options.Parser);
|
||||
|
||||
ActionConnectorEvents.CaseClosed += OnCaseClosed;
|
||||
ActionConnectorEvents.CaseCreated += OnCaseCreated;
|
||||
ActionConnectorEvents.ApplicationStartup += OnApplicationStartup;
|
||||
|
||||
_running = true;
|
||||
LogEntry("ActionBus started.", C4IT.Logging.LogLevels.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LogMethodEnd(CM);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from all trigger events.
|
||||
/// Call during application shutdown.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
ActionConnectorEvents.CaseClosed -= OnCaseClosed;
|
||||
ActionConnectorEvents.CaseCreated -= OnCaseCreated;
|
||||
ActionConnectorEvents.ApplicationStartup -= OnApplicationStartup;
|
||||
|
||||
_running = false;
|
||||
LogEntry("ActionBus stopped.", C4IT.Logging.LogLevels.Info);
|
||||
}
|
||||
|
||||
public void Dispose() => Stop();
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// Event handlers — each one creates a typed ActionContext and fires dispatch
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void OnCaseClosed(object sender, TriggerEventArgs<CaseClosedPayload> args)
|
||||
=> DispatchTrigger(TriggerEvent.CaseClosed, args.Payload, sender);
|
||||
|
||||
private void OnCaseCreated(object sender, TriggerEventArgs<CaseCreatedPayload> args)
|
||||
=> DispatchTrigger(TriggerEvent.CaseCreated, args.Payload, sender);
|
||||
|
||||
private void OnApplicationStartup(object sender, TriggerEventArgs<ApplicationStartupPayload> args)
|
||||
=> DispatchTrigger(TriggerEvent.ApplicationStartup, args.Payload, sender);
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// Core dispatch — fire-and-forget wrapper that surfaces results via events
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void DispatchTrigger(TriggerEvent triggerEvent, PayloadBase payload, object sender)
|
||||
{
|
||||
if (!_running) return;
|
||||
if (!_configuration.Triggers.TryGetValue(triggerEvent, out Trigger trigger)) return;
|
||||
|
||||
// Run async dispatch on the thread pool; do not block the caller.
|
||||
Task.Run(() => DispatchActionsAsync(trigger.Actions, triggerEvent, payload, sender));
|
||||
}
|
||||
|
||||
private async Task DispatchActionsAsync(
|
||||
IDictionary<string, ActionDefinitionBase> actions,
|
||||
TriggerEvent triggerEvent,
|
||||
PayloadBase payload,
|
||||
object sender)
|
||||
{
|
||||
foreach (var pair in actions)
|
||||
{
|
||||
ActionDefinitionBase action = pair.Value;
|
||||
|
||||
var context = new ActionContext
|
||||
{
|
||||
TriggerEvent = triggerEvent,
|
||||
AwaitResult = action.AwaitResult,
|
||||
Payload = payload,
|
||||
};
|
||||
|
||||
ActionResult result = await ExecuteSafeAsync(action, context);
|
||||
|
||||
ActionConnectorEvents.RaiseActionCompleted(
|
||||
sender,
|
||||
new ActionCompletedEventArgs(result, context));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ActionResult> ExecuteSafeAsync(ActionDefinitionBase action, ActionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _dispatcher.DispatchAsync(action, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
return ActionResult.Fail(action.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user