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">