diff --git a/F4SD-ActionConnector/Bus/ActionBus.cs b/F4SD-ActionConnector/Bus/ActionBus.cs
new file mode 100644
index 0000000..c85f0dc
--- /dev/null
+++ b/F4SD-ActionConnector/Bus/ActionBus.cs
@@ -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
+{
+ ///
+ /// Entry point for the ActionConnector.
+ /// Subscribes to , reads the configuration once,
+ /// and dispatches actions whenever a trigger event fires.
+ ///
+ /// Usage from the host application:
+ ///
+ /// 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();
+ ///
+ ///
+ 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);
+ }
+
+ ///
+ /// Loads the configuration and subscribes to all trigger events.
+ /// Call once during application startup.
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Unsubscribes from all trigger events.
+ /// Call during application shutdown.
+ ///
+ 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 args)
+ => DispatchTrigger(TriggerEvent.CaseClosed, args.Payload, sender);
+
+ private void OnCaseCreated(object sender, TriggerEventArgs args)
+ => DispatchTrigger(TriggerEvent.CaseCreated, args.Payload, sender);
+
+ private void OnApplicationStartup(object sender, TriggerEventArgs 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 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 ExecuteSafeAsync(ActionDefinitionBase action, ActionContext context)
+ {
+ try
+ {
+ return await _dispatcher.DispatchAsync(action, context);
+ }
+ catch (Exception ex)
+ {
+ LogException(ex);
+ return ActionResult.Fail(action.Id, ex.Message);
+ }
+ }
+ }
+}
diff --git a/F4SD-ActionConnector/Bus/ActionBusOptions.cs b/F4SD-ActionConnector/Bus/ActionBusOptions.cs
new file mode 100644
index 0000000..ba99d26
--- /dev/null
+++ b/F4SD-ActionConnector/Bus/ActionBusOptions.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+using System.Xml;
+using C4IT.XML;
+using F4SD.ActionConnector.Execution;
+using F4SD.ActionConnector.Models;
+
+namespace F4SD.ActionConnector.Bus
+{
+ public sealed class ActionBusOptions
+ {
+ public XmlElement ConfigRootElement { get; set; }
+ public cXmlParser Parser { get; set; }
+
+ ///
+ /// Called for every QuickAction that fires.
+ /// Template variables in are already
+ /// resolved against the event payload before this delegate is invoked.
+ /// Must be set when any QuickAction is configured.
+ ///
+ public Func> QuickActionExecutor { get; set; }
+ }
+}
diff --git a/F4SD-ActionConnector/Execution/ActionDispatcher.cs b/F4SD-ActionConnector/Execution/ActionDispatcher.cs
new file mode 100644
index 0000000..0450280
--- /dev/null
+++ b/F4SD-ActionConnector/Execution/ActionDispatcher.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading.Tasks;
+using F4SD.ActionConnector.Enumerations;
+using F4SD.ActionConnector.Models;
+using static C4IT.Logging.cLogManager;
+
+namespace F4SD.ActionConnector.Execution
+{
+ ///
+ /// Routes a single action definition to the correct executor based on its .
+ ///
+ internal sealed class ActionDispatcher
+ {
+ private readonly Func> _quickActionExecutor;
+
+ internal ActionDispatcher(Func> quickActionExecutor)
+ {
+ _quickActionExecutor = quickActionExecutor;
+ }
+
+ internal async Task DispatchAsync(ActionDefinitionBase action, ActionContext context)
+ {
+ if (!action.IsEnabled)
+ return ActionResult.Skip(action.Id);
+
+ switch (action.Type)
+ {
+ case ActionType.QuickAction:
+ return await ExecuteQuickActionAsync((QuickActionDefinition)action, context);
+
+ case ActionType.HttpCall:
+ case ActionType.Webhook:
+ throw new NotImplementedException(
+ $"ActionType '{action.Type}' is not yet implemented.");
+
+ default:
+ LogEntry($"ActionDispatcher: unknown ActionType '{action.Type}' for action '{action.Id}'.",
+ C4IT.Logging.LogLevels.Warning);
+ return ActionResult.Skip(action.Id);
+ }
+ }
+
+ private async Task ExecuteQuickActionAsync(QuickActionDefinition definition, ActionContext context)
+ {
+ if (_quickActionExecutor == null)
+ throw new InvalidOperationException(
+ $"A QuickAction ('{definition.Id}') was triggered but no {nameof(Bus.ActionBusOptions.QuickActionExecutor)} was registered.");
+
+ var resolvedParameters = PayloadVariableResolver.Resolve(definition.Parameters, context.Payload);
+
+ var request = new QuickActionRequest
+ {
+ ActionId = definition.Id,
+ QuickActionRef = definition.QuickActionRef,
+ Parameters = resolvedParameters,
+ Context = context,
+ };
+
+ return await _quickActionExecutor(request);
+ }
+ }
+}
diff --git a/F4SD-ActionConnector/Execution/PayloadVariableResolver.cs b/F4SD-ActionConnector/Execution/PayloadVariableResolver.cs
new file mode 100644
index 0000000..a4e245a
--- /dev/null
+++ b/F4SD-ActionConnector/Execution/PayloadVariableResolver.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using F4SD.ActionConnector.Payloads;
+
+namespace F4SD.ActionConnector.Execution
+{
+ ///
+ /// Resolves template variables such as {{Case.Id}} or {{Case.ClosedAt:yyyy-MM-ddTHH:mm:ssZ}}
+ /// against a payload object.
+ ///
+ /// Variable format: {{ObjectName.PropertyName[:FormatSpecifier]}}
+ ///
+ internal static class PayloadVariableResolver
+ {
+ private static readonly Regex VariablePattern =
+ new Regex(@"\{\{(?[^.}]+)\.(?[^:}]+)(?::(?[^}]+))?\}\}",
+ RegexOptions.Compiled);
+
+ ///
+ /// Returns a new dictionary with every value in having
+ /// its template variables replaced by the corresponding payload property values.
+ /// Pure single-variable templates (e.g. {{Case.ClosedAt}}) yield the raw typed
+ /// object; mixed or formatted templates yield a resolved string.
+ ///
+ internal static IDictionary Resolve(
+ IDictionary rawParameters,
+ PayloadBase payload)
+ {
+ var resolved = new Dictionary(rawParameters.Count);
+
+ foreach (var pair in rawParameters)
+ resolved[pair.Key] = ResolveValue(pair.Value, payload);
+
+ return resolved;
+ }
+
+ private static object ResolveValue(string template, PayloadBase payload)
+ {
+ var match = VariablePattern.Match(template);
+
+ // Pure single variable with no format specifier → return raw object
+ if (match.Success && match.Value == template && !match.Groups["fmt"].Success)
+ return ResolveRawProperty(match, payload);
+
+ // Mixed template, literal, or explicit format specifier → return resolved string
+ return ResolveString(template, payload);
+ }
+
+ private static object ResolveRawProperty(Match match, PayloadBase payload)
+ {
+ string propertyName = match.Groups["prop"].Value;
+
+ PropertyInfo property = payload.GetType().GetProperty(
+ propertyName,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
+
+ if (property == null)
+ return match.Value;
+
+ return property.GetValue(payload);
+ }
+
+ private static string ResolveString(string template, PayloadBase payload)
+ {
+ return VariablePattern.Replace(template, match => ResolveMatch(match, payload));
+ }
+
+ private static string ResolveMatch(Match match, PayloadBase payload)
+ {
+ string propertyName = match.Groups["prop"].Value;
+ string formatSpecifier = match.Groups["fmt"].Success ? match.Groups["fmt"].Value : null;
+
+ PropertyInfo property = payload.GetType().GetProperty(
+ propertyName,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
+
+ if (property == null)
+ return match.Value;
+
+ object value = property.GetValue(payload);
+
+ if (value == null)
+ return string.Empty;
+
+ if (formatSpecifier != null && value is IFormattable formattable)
+ return formattable.ToString(formatSpecifier, CultureInfo.InvariantCulture);
+
+ return value.ToString();
+ }
+ }
+}
diff --git a/F4SD-ActionConnector/Execution/QuickActionRequest.cs b/F4SD-ActionConnector/Execution/QuickActionRequest.cs
new file mode 100644
index 0000000..9e0951d
--- /dev/null
+++ b/F4SD-ActionConnector/Execution/QuickActionRequest.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using F4SD.ActionConnector.Models;
+
+namespace F4SD.ActionConnector.Execution
+{
+ public sealed class QuickActionRequest
+ {
+ ///
+ /// Matches from the configuration.
+ ///
+ public string ActionId { get; internal set; }
+
+ ///
+ /// The name of the Quick Action to invoke, e.g. "Document case in JIRA".
+ ///
+ public string QuickActionRef { get; internal set; }
+
+ ///
+ /// Template variables resolved against the event payload.
+ /// Values are the raw payload values; callers are responsible for casting.
+ ///
+ public IDictionary Parameters { get; internal set; }
+
+ ///
+ /// Full context of the triggering event.
+ ///
+ public ActionContext Context { get; internal set; }
+ }
+}
diff --git a/F4SD-ActionConnector/Models/ActionContext.cs b/F4SD-ActionConnector/Models/ActionContext.cs
index 5487625..e53852b 100644
--- a/F4SD-ActionConnector/Models/ActionContext.cs
+++ b/F4SD-ActionConnector/Models/ActionContext.cs
@@ -5,8 +5,8 @@ namespace F4SD.ActionConnector.Models
{
public sealed class ActionContext
{
- public TriggerEvent TriggerEvent { get; set; }
- public bool AwaitResult { get; set; }
- public PayloadBase Payload { get; set; }
+ public TriggerEvent TriggerEvent { get; internal set; }
+ public bool AwaitResult { get; internal set; }
+ public PayloadBase Payload { get; internal set; }
}
}
diff --git a/F4SD-ActionConnector/Models/ActionDefinitionBase.cs b/F4SD-ActionConnector/Models/ActionDefinitionBase.cs
index 48e7f09..fb00f86 100644
--- a/F4SD-ActionConnector/Models/ActionDefinitionBase.cs
+++ b/F4SD-ActionConnector/Models/ActionDefinitionBase.cs
@@ -8,31 +8,31 @@ namespace F4SD.ActionConnector.Models
{
public abstract class ActionDefinitionBase
{
- public string Id { get; set; }
+ public string Id { get; internal set; }
public abstract ActionType Type { get; }
- public bool AwaitResult { get; set; }
- public bool IsEnabled { get; set; }
- public string Description { get; set; }
+ public bool AwaitResult { get; internal set; }
+ public bool IsEnabled { get; internal set; }
+ public string Description { get; internal set; }
public bool IsValid { get; private protected set; }
protected ActionDefinitionBase(XmlElement xNode, cXmlParser parser)
- {
- try
- {
- Id = cXmlParser.GetStringFromXmlAttribute(xNode, "Id");
- AwaitResult = cXmlParser.GetBoolFromXmlAttribute(xNode, "AwaitResult");
- IsEnabled = cXmlParser.GetBoolFromXmlAttribute(xNode, "IsEnabled");
-
- XmlNode descriptionNode = xNode.SelectSingleNode("Description");
- if (descriptionNode != null)
- Description = cXmlParser.GetInnerTextFromXmlElement(descriptionNode);
-
- IsValid = true;
- }
- catch (Exception ex)
- {
- LogException(ex);
- }
+ {
+ try
+ {
+ Id = cXmlParser.GetStringFromXmlAttribute(xNode, "Id");
+ AwaitResult = cXmlParser.GetBoolFromXmlAttribute(xNode, "AwaitResult");
+ IsEnabled = cXmlParser.GetBoolFromXmlAttribute(xNode, "IsEnabled");
+
+ XmlNode descriptionNode = xNode.SelectSingleNode("Description");
+ if (descriptionNode != null)
+ Description = cXmlParser.GetInnerTextFromXmlElement(descriptionNode);
+
+ IsValid = true;
+ }
+ catch (Exception ex)
+ {
+ LogException(ex);
+ }
}
}
}
diff --git a/F4SD-ActionConnector/Models/ActionResult.cs b/F4SD-ActionConnector/Models/ActionResult.cs
index 3b9559d..a667d25 100644
--- a/F4SD-ActionConnector/Models/ActionResult.cs
+++ b/F4SD-ActionConnector/Models/ActionResult.cs
@@ -5,29 +5,29 @@ namespace F4SD.ActionConnector.Models
{
public sealed class ActionResult
{
- public string ActionId { get; set; }
- public ActionResultStatus Status { get; set; }
+ public string ActionId { get; private set; }
+ public ActionResultStatus Status { get; private set; }
///
/// HTTP status code for HttpCall actions; null for other types.
///
- public int? HttpStatusCode { get; set; }
+ public int? HttpStatusCode { get; private set; }
///
/// Raw response body (HttpCall) or serialized output (QuickAction).
///
- public string RawResponse { get; set; }
+ public string RawResponse { get; private set; }
///
/// Named fields extracted from the response via jsonPath mappings.
/// Populated when DisplayInCockpit is true.
///
- public IDictionary MappedFields { get; set; } = new Dictionary();
+ public IDictionary MappedFields { get; private set; } = new Dictionary();
///
/// Human-readable error message; set when Status is Failure.
///
- public string ErrorMessage { get; set; }
+ public string ErrorMessage { get; private set; }
public static ActionResult Ok(string actionId, string rawResponse = null)
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Success, RawResponse = rawResponse };
diff --git a/F4SD-ActionConnector/Models/ExternalCommunicationConfiguration.cs b/F4SD-ActionConnector/Models/ExternalCommunicationConfiguration.cs
index 87d5708..8f6d844 100644
--- a/F4SD-ActionConnector/Models/ExternalCommunicationConfiguration.cs
+++ b/F4SD-ActionConnector/Models/ExternalCommunicationConfiguration.cs
@@ -3,17 +3,16 @@ using F4SD.ActionConnector.Enumerations;
using System;
using System.Collections.Generic;
using System.Reflection;
-using System.Text;
using System.Xml;
using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
- internal class ExternalCommunicationConfiguration
+ public class ExternalCommunicationConfiguration
{
private const string FileNameConfig = "F4SD-ExternalCommunication-Configuration.xml";
private const string FileNameConfigSchema = "F4SD-ExternalCommunication-Configuration.xsd";
- private const string ConfigRootElement = "F4SD-ExternalCommunication-Configuration";
+ public const string ConfigRootElement = "F4SD-ExternalCommunication-Configuration";
public IDictionary Triggers { get; } = new Dictionary();
@@ -43,7 +42,7 @@ namespace F4SD.ActionConnector.Models
{
Trigger trigger = new Trigger(triggerNodeElement, parser);
- if (trigger is null || !Triggers.ContainsKey(trigger.Event))
+ if (trigger is null || !trigger.IsValid || Triggers.ContainsKey(trigger.Event))
continue;
Triggers.Add(trigger.Event, trigger);
diff --git a/F4SD-ActionConnector/Models/HttpCallDefinition.cs b/F4SD-ActionConnector/Models/HttpCallDefinition.cs
index 2d0e4d2..17786e5 100644
--- a/F4SD-ActionConnector/Models/HttpCallDefinition.cs
+++ b/F4SD-ActionConnector/Models/HttpCallDefinition.cs
@@ -11,20 +11,20 @@ namespace F4SD.ActionConnector.Models
{
public override ActionType Type => ActionType.HttpCall;
- public string Url { get; set; }
- public HttpMethod Method { get; set; } = HttpMethod.Post;
- public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
- public IDictionary Headers { get; set; } = new Dictionary();
- public string Body { get; set; }
+ public string Url { get; internal set; }
+ public HttpMethod Method { get; internal set; } = HttpMethod.Post;
+ public TimeSpan Timeout { get; internal set; } = TimeSpan.FromSeconds(30);
+ public IDictionary Headers { get; internal set; } = new Dictionary();
+ public string Body { get; internal set; }
///
/// jsonPath expressions keyed by field name, used to extract values from the response.
///
- public IDictionary MappedFieldJsonPaths { get; set; } = new Dictionary();
+ public IDictionary MappedFieldJsonPaths { get; internal set; } = new Dictionary();
internal HttpCallDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
- {
-
+ {
+
}
}
}
diff --git a/F4SD-ActionConnector/Models/QuickActionDefinition.cs b/F4SD-ActionConnector/Models/QuickActionDefinition.cs
index 33df28f..cdeb98c 100644
--- a/F4SD-ActionConnector/Models/QuickActionDefinition.cs
+++ b/F4SD-ActionConnector/Models/QuickActionDefinition.cs
@@ -1,27 +1,27 @@
-using C4IT.XML;
-using F4SD.ActionConnector.Enumerations;
-using System;
-using System.Collections.Generic;
-using System.Xml;
-using static C4IT.Logging.cLogManager;
-
-namespace F4SD.ActionConnector.Models
-{
- public sealed class QuickActionDefinition : ActionDefinitionBase
- {
- private const string ParametersNodeName = "Parameters";
- private const string ParameterNodeName = "Parameter";
-
- public override ActionType Type => ActionType.QuickAction;
-
- ///
- /// Reference to the Quick Action to invoke, e.g. "Protocol case in JIRA".
- ///
- public string QuickActionRef { get; set; }
-
- public IDictionary Parameters { get; private set; } = new Dictionary();
-
- internal QuickActionDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
+using C4IT.XML;
+using F4SD.ActionConnector.Enumerations;
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using static C4IT.Logging.cLogManager;
+
+namespace F4SD.ActionConnector.Models
+{
+ public sealed class QuickActionDefinition : ActionDefinitionBase
+ {
+ private const string ParametersNodeName = "Parameters";
+ private const string ParameterNodeName = "Parameter";
+
+ public override ActionType Type => ActionType.QuickAction;
+
+ ///
+ /// Reference to the Quick Action to invoke, e.g. "Protocol case in JIRA".
+ ///
+ public string QuickActionRef { get; internal set; }
+
+ public IDictionary Parameters { get; private set; } = new Dictionary();
+
+ internal QuickActionDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
{
try
{
@@ -81,7 +81,7 @@ namespace F4SD.ActionConnector.Models
{
try
{
- string parameterName = cXmlParser.GetStringFromXmlAttribute(parameterNode, "name");
+ string parameterName = cXmlParser.GetStringFromXmlAttribute(parameterNode, "Name");
string parameterVariable = cXmlParser.GetInnerTextFromXmlElement(parameterNode);
if (string.IsNullOrEmpty(parameterName) || string.IsNullOrEmpty(parameterVariable))
@@ -102,4 +102,4 @@ namespace F4SD.ActionConnector.Models
}
}
}
-}
+}
diff --git a/F4SD-ActionConnector/Models/Trigger.cs b/F4SD-ActionConnector/Models/Trigger.cs
index 8f54af1..8cb7e6a 100644
--- a/F4SD-ActionConnector/Models/Trigger.cs
+++ b/F4SD-ActionConnector/Models/Trigger.cs
@@ -8,7 +8,7 @@ using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
- internal class Trigger
+ public class Trigger
{
public TriggerEvent Event { get; set; }
@@ -23,7 +23,7 @@ namespace F4SD.ActionConnector.Models
{
try
{
- Event = cXmlParser.GetEnumFromAttribute(xNode, "action", TriggerEvent.Unknown);
+ Event = cXmlParser.GetEnumFromAttribute(xNode, "event", TriggerEvent.Unknown);
XmlNode actionsNode = xNode.SelectSingleNode(ActionsNodeName);
@@ -56,7 +56,7 @@ namespace F4SD.ActionConnector.Models
{
try
{
- ActionType type = cXmlParser.GetEnumFromAttribute(actionNode, "type", ActionType.Unknown);
+ ActionType type = cXmlParser.GetEnumFromAttribute(actionNode, "Type", ActionType.Unknown);
ActionDefinitionBase actionDefinition = null;
diff --git a/FasdDesktopUi/App.xaml.cs b/FasdDesktopUi/App.xaml.cs
index a9f9f37..07c6c34 100644
--- a/FasdDesktopUi/App.xaml.cs
+++ b/FasdDesktopUi/App.xaml.cs
@@ -4,10 +4,14 @@ using C4IT.FASD.Security;
using C4IT.Graphics;
using C4IT.Logging;
using C4IT.MultiLanguage;
+using C4IT.XML;
+using F4SD.ActionConnector.Bus;
+using F4SD.ActionConnector.Models;
using FasdDesktopUi.Basics;
using FasdDesktopUi.Basics.Helper;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Basics.Services.Models;
+using FasdDesktopUi.Basics.UiActions.NativeActions;
using FasdDesktopUi.Pages.CustomMessageBox;
using FasdDesktopUi.Pages.PhoneSettingsPage;
using FasdDesktopUi.Pages.SearchPage;
@@ -45,6 +49,8 @@ namespace FasdDesktopUi
private TileScope? _pendingTicketOverviewScope;
#endregion
+
+ private ActionBus _actionBus;
private async void Application_Startup(object sender, StartupEventArgs e)
{
@@ -62,7 +68,7 @@ namespace FasdDesktopUi
Thread.Sleep(100);
}
Console.WriteLine("Debugger attached");
- */
+ */
Assembly assembly = Assembly.GetExecutingAssembly();
var ProductName = FileVersionInfo.GetVersionInfo(assembly.Location).ProductName;
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
@@ -127,7 +133,24 @@ namespace FasdDesktopUi
InitializeNotifyIcon();
- //CustomMessageBox.ShowCustomContent("Close case", enumHealthCardStateLevel.None, null, true, new CloseCaseDialogWithTicket());
+ try
+ {
+ //_actionBus = new ActionBus(new ActionBusOptions()
+ //{
+ // ConfigRootElement = cXmlParser.OpenXmlDocument(new List() { new cXmlFileLocation()
+ // {
+ // LocationType = cXmlFileLocation.enumXmlLocationType.StringContent,
+ // StringContent = string.Empty
+ // } }, out cXmlParser parser, ExternalCommunicationConfiguration.ConfigRootElement),
+ // Parser = parser,
+ // QuickActionExecutor = QuickActionExecutor.RunAction
+ //});
+ //_actionBus.Start();
+ }
+ catch (Exception ex)
+ {
+ LogException(ex);
+ }
await cAppStartUp.StartAsync(e.Args);
notifyIcon.Visible = true;
@@ -491,19 +514,19 @@ namespace FasdDesktopUi
}
}
-#endregion
+ #endregion
private async void Application_Exit(object sender, ExitEventArgs e)
{
cF4sdUserInfo userInfo = null;
ConfiguredTaskAwaitable? closeUserSessionTask = null;
- lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
- {
- userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo;
- }
- if (userInfo?.Id.Equals(Guid.Empty) is false)
+ lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
{
- closeUserSessionTask = cFasdCockpitCommunicationBase.Instance?.CloseUserSession(cFasdCockpitConfig.SessionId).ConfigureAwait(false);
+ userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo;
+ }
+ if (userInfo?.Id.Equals(Guid.Empty) is false)
+ {
+ closeUserSessionTask = cFasdCockpitCommunicationBase.Instance?.CloseUserSession(cFasdCockpitConfig.SessionId).ConfigureAwait(false);
}
if (cFasdCockpitCommunicationBase.Instance != null)
@@ -545,6 +568,8 @@ namespace FasdDesktopUi
}
catch { }
+ _actionBus?.Stop();
+
if (closeUserSessionTask is ConfiguredTaskAwaitable _t)
await _t;
}
diff --git a/FasdDesktopUi/Basics/Services/SupportCase/Processors/SupportCaseProcessorFactory.cs b/FasdDesktopUi/Basics/Services/SupportCase/Processors/SupportCaseProcessorFactory.cs
index c213153..2086d93 100644
--- a/FasdDesktopUi/Basics/Services/SupportCase/Processors/SupportCaseProcessorFactory.cs
+++ b/FasdDesktopUi/Basics/Services/SupportCase/Processors/SupportCaseProcessorFactory.cs
@@ -1,4 +1,5 @@
-using System;
+using FasdDesktopUi.Basics.UiActions.NativeActions;
+using System;
using System.Collections.Generic;
namespace FasdDesktopUi.Basics.Services.SupportCase.Processors
@@ -12,6 +13,7 @@ namespace FasdDesktopUi.Basics.Services.SupportCase.Processors
if (!_supportCaseProccesors.ContainsKey(id))
_supportCaseProccesors.Add(id, new SupportCaseProcessor());
+ QuickActionExecutor.SetSupportCaseProcessor(_supportCaseProccesors[id]);
return _supportCaseProccesors[id];
}
}
diff --git a/FasdDesktopUi/Basics/SupportCaseDataProvider.cs b/FasdDesktopUi/Basics/SupportCaseDataProvider.cs
index a6d6014..8c00024 100644
--- a/FasdDesktopUi/Basics/SupportCaseDataProvider.cs
+++ b/FasdDesktopUi/Basics/SupportCaseDataProvider.cs
@@ -1,37 +1,38 @@
-using System;
+using C4IT.F4SD.SupportCaseProtocoll.Models;
+using C4IT.FASD.Base;
+using C4IT.FASD.Cockpit.Communication;
+using C4IT.Logging;
+using C4IT.MultiLanguage;
+using F4SD.ActionConnector.Bus;
+using F4SD.ActionConnector.Events;
+using F4SD.ActionConnector.Payloads;
+using F4SD.Gamification;
+using F4SD.Gamification.Services;
+using FasdDesktopUi.Basics.Helper;
+using FasdDesktopUi.Basics.Models;
+using FasdDesktopUi.Basics.Services.ProtocollService;
+using FasdDesktopUi.Basics.Services.RelationService;
+using FasdDesktopUi.Basics.Services.SupportCase;
+using FasdDesktopUi.Basics.Services.SupportCase.Controllers;
+using FasdDesktopUi.Basics.Services.SupportCase.Processors;
+using FasdDesktopUi.Basics.UserControls;
+using FasdDesktopUi.Config;
+using FasdDesktopUi.Pages;
+using FasdDesktopUi.Pages.CustomMessageBox;
+using FasdDesktopUi.Pages.SearchPage;
+using FasdDesktopUi.Pages.TicketCompletion;
+using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
-using System.Linq;
using System.Windows.Input;
-using System.Diagnostics;
-
-using C4IT.FASD.Base;
-using C4IT.Logging;
-using C4IT.FASD.Cockpit.Communication;
-using C4IT.MultiLanguage;
using static C4IT.Logging.cLogManager;
-using FasdDesktopUi.Basics.Helper;
-using FasdDesktopUi.Basics.Models;
-using FasdDesktopUi.Basics.UserControls;
-using FasdDesktopUi.Pages;
-using FasdDesktopUi.Pages.CustomMessageBox;
-using FasdDesktopUi.Config;
-using FasdDesktopUi.Pages.SearchPage;
-using FasdDesktopUi.Pages.TicketCompletion;
-using Newtonsoft.Json;
-using FasdDesktopUi.Basics.Services.ProtocollService;
-using FasdDesktopUi.Basics.Services.SupportCase;
-using FasdDesktopUi.Basics.Services.RelationService;
-using FasdDesktopUi.Basics.Services.SupportCase.Controllers;
-using C4IT.F4SD.SupportCaseProtocoll.Models;
-using FasdDesktopUi.Basics.Services.SupportCase.Processors;
-using F4SD.Gamification;
-using F4SD.Gamification.Services;
-
namespace FasdDesktopUi.Basics
{
@@ -303,6 +304,8 @@ namespace FasdDesktopUi.Basics
detailsPage?.EndPause();
+ ActionConnectorEvents.RaiseCaseClosed(this, new CaseClosedPayload() { ClosedAt = DateTime.UtcNow });
+
List tasks = new List();
tasks.Add(DirectConnectionHelper.DirectConnectionStopAsync());
diff --git a/FasdDesktopUi/Basics/UiActions/NativeActions/QuickActionExecutor.cs b/FasdDesktopUi/Basics/UiActions/NativeActions/QuickActionExecutor.cs
new file mode 100644
index 0000000..ca33229
--- /dev/null
+++ b/FasdDesktopUi/Basics/UiActions/NativeActions/QuickActionExecutor.cs
@@ -0,0 +1,71 @@
+using C4IT.FASD.Base;
+using F4SD.ActionConnector.Execution;
+using F4SD.ActionConnector.Models;
+using F4SD.ActionConnector.Payloads;
+using FasdDesktopUi.Basics.Models;
+using FasdDesktopUi.Basics.Services.SupportCase.Controllers;
+using FasdDesktopUi.Basics.Services.SupportCase.Processors;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using static C4IT.Logging.cLogManager;
+
+namespace FasdDesktopUi.Basics.UiActions.NativeActions
+{
+ internal static class QuickActionExecutor
+ {
+ private static ISupportCaseProcessor _supportCaseProcessor;
+
+ internal static void SetSupportCaseProcessor(ISupportCaseProcessor supportCaseProcessor)
+ {
+ _supportCaseProcessor = supportCaseProcessor ?? throw new ArgumentNullException(nameof(supportCaseProcessor));
+ }
+
+ internal static async Task RunAction(QuickActionRequest quickActionRequest)
+ {
+ try
+ {
+ if (_supportCaseProcessor is null)
+ return ActionResult.Fail(quickActionRequest.ActionId, "No valid SupportCase processor was found.");
+
+ if (!(quickActionRequest.Context.Payload is CaseClosedPayload payload))
+ return ActionResult.Fail(quickActionRequest.ActionId, $"Payload is not of type '{typeof(CaseClosedPayload)}'. It is of type '{quickActionRequest.Context.Payload.GetType()}'");
+
+ cMenuDataBase menuData = MenuDataFactory.GetByName(quickActionRequest.QuickActionRef, _supportCaseProcessor.SupportCaseDataProviderArtifact.NamedParameterEntries, _supportCaseProcessor.SupportCaseDataProviderArtifact.CaseRelations.Select(r => r.Key));
+
+ // Till there is a clear picture of what object belongs to a case and what doesn't
+ // we do not want to make remote Quick Actions available
+ if (menuData is null || !(menuData.UiAction is cUiLocalQuickAction quickAction))
+ {
+ LogEntry($"'{quickActionRequest.QuickActionRef}' is no valid Local QuickAction and was therefore skipped.", C4IT.Logging.LogLevels.Error);
+ return ActionResult.Skip(quickActionRequest.ActionId);
+ }
+
+ await quickAction.RunUiActionAsync(null, null, false, _supportCaseProcessor.SupportCaseDataProviderArtifact);
+ await quickAction.ProcessActionAsync(CancellationToken.None, GetParameterDictionaryFrom(quickActionRequest.Parameters));
+
+ return ActionResult.Ok(quickActionRequest.ActionId);
+ }
+ catch (Exception ex)
+ {
+ LogException(ex);
+ return ActionResult.Fail(quickActionRequest.ActionId, ex.Message);
+ }
+ }
+
+ private static Dictionary GetParameterDictionaryFrom(IDictionary requestParameters)
+ {
+ try
+ {
+ return requestParameters.ToDictionary(p => new cAdjustableParameterString() { ParameterName = p.Key } as cAdjustableParameter, p => p.Value);
+ }
+ catch (Exception ex)
+ {
+ LogException(ex);
+ return new Dictionary();
+ }
+ }
+ }
+}
diff --git a/FasdDesktopUi/Basics/UiActions/UiLocalQuickAction.cs b/FasdDesktopUi/Basics/UiActions/UiLocalQuickAction.cs
index 5a234e1..1aadf70 100644
--- a/FasdDesktopUi/Basics/UiActions/UiLocalQuickAction.cs
+++ b/FasdDesktopUi/Basics/UiActions/UiLocalQuickAction.cs
@@ -137,7 +137,7 @@ namespace FasdDesktopUi.Basics.UiActions
quickActionOutput = new cQuickActionOutputSingle(new cF4sdQuickActionRevision.cOutput() { ResultCode = enumQuickActionSuccess.finished });
else
{
- StatusMonitor.QuickActionOutputs.Add(ResultRevision.Output);
+ StatusMonitor?.QuickActionOutputs?.Add(ResultRevision.Output);
quickActionOutput = cQuickActionOutput.GetQuickActionOutput(ResultRevision.Output, DataProvider);
}
@@ -152,7 +152,7 @@ namespace FasdDesktopUi.Basics.UiActions
cMultiLanguageSupport.CurrentLanguage = tempLang;
}
- cQuickActionCopyData copyData = QuickActionProtocollEntryOutput.GetCopyData(LocalQuickAction, DataProvider, false, protocollOutput, StatusMonitor.MeasureValues);
+ cQuickActionCopyData copyData = QuickActionProtocollEntryOutput.GetCopyData(LocalQuickAction, DataProvider, false, protocollOutput, StatusMonitor?.MeasureValues);
QuickActionProtocollEntry protocollEntry = QuickActionProtocollEntryOutput.GetQuickActionProtocollEntry(LocalQuickAction, copyData);
F4SDProtocoll.Instance.Add(protocollEntry);
@@ -192,7 +192,7 @@ namespace FasdDesktopUi.Basics.UiActions
cMultiLanguageSupport.CurrentLanguage = tempLang;
}
- cQuickActionCopyData copyData = QuickActionProtocollEntryOutput.GetCopyData(LocalQuickAction, DataProvider, false, protocollOutput, StatusMonitor.MeasureValues);
+ cQuickActionCopyData copyData = QuickActionProtocollEntryOutput.GetCopyData(LocalQuickAction, DataProvider, false, protocollOutput, StatusMonitor?.MeasureValues);
QuickActionProtocollEntry protocollEntry = QuickActionProtocollEntryOutput.GetQuickActionProtocollEntry(LocalQuickAction, copyData);
F4SDProtocoll.Instance.Add(protocollEntry);
diff --git a/FasdDesktopUi/Basics/UserControls/Notepad.xaml b/FasdDesktopUi/Basics/UserControls/Notepad.xaml
index d0b7ec7..93f173a 100644
--- a/FasdDesktopUi/Basics/UserControls/Notepad.xaml
+++ b/FasdDesktopUi/Basics/UserControls/Notepad.xaml
@@ -152,8 +152,7 @@
Background="{DynamicResource BackgroundColor.DetailsPage.Widget.Title}"
SpellCheck.IsEnabled="True"
SelectionChanged="NotepadRichTextBox_SelectionChanged"
- IsVisibleChanged="NotepadRichTextBox_IsVisibleChanged"
- GotKeyboardFocus="NotepadRichTextBox_GotKeyboardFocus">
+ IsVisibleChanged="NotepadRichTextBox_IsVisibleChanged">