Add event-driven quick action dispatch

This commit is contained in:
Drechsler, Meik
2026-07-22 11:57:32 +02:00
parent fb45f1c42b
commit 9bd3896ba0
20 changed files with 591 additions and 139 deletions

View 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);
}
}
}
}