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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
F4SD-ActionConnector/Bus/ActionBusOptions.cs
Normal file
23
F4SD-ActionConnector/Bus/ActionBusOptions.cs
Normal file
@@ -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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called for every QuickAction that fires.
|
||||||
|
/// Template variables in <see cref="QuickActionRequest.Parameters"/> are already
|
||||||
|
/// resolved against the event payload before this delegate is invoked.
|
||||||
|
/// Must be set when any QuickAction is configured.
|
||||||
|
/// </summary>
|
||||||
|
public Func<QuickActionRequest, Task<ActionResult>> QuickActionExecutor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
62
F4SD-ActionConnector/Execution/ActionDispatcher.cs
Normal file
62
F4SD-ActionConnector/Execution/ActionDispatcher.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Routes a single action definition to the correct executor based on its <see cref="ActionType"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ActionDispatcher
|
||||||
|
{
|
||||||
|
private readonly Func<QuickActionRequest, Task<ActionResult>> _quickActionExecutor;
|
||||||
|
|
||||||
|
internal ActionDispatcher(Func<QuickActionRequest, Task<ActionResult>> quickActionExecutor)
|
||||||
|
{
|
||||||
|
_quickActionExecutor = quickActionExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<ActionResult> 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<ActionResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
F4SD-ActionConnector/Execution/PayloadVariableResolver.cs
Normal file
94
F4SD-ActionConnector/Execution/PayloadVariableResolver.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves template variables such as {{Case.Id}} or {{Case.ClosedAt:yyyy-MM-ddTHH:mm:ssZ}}
|
||||||
|
/// against a payload object.
|
||||||
|
///
|
||||||
|
/// Variable format: {{ObjectName.PropertyName[:FormatSpecifier]}}
|
||||||
|
/// </summary>
|
||||||
|
internal static class PayloadVariableResolver
|
||||||
|
{
|
||||||
|
private static readonly Regex VariablePattern =
|
||||||
|
new Regex(@"\{\{(?<obj>[^.}]+)\.(?<prop>[^:}]+)(?::(?<fmt>[^}]+))?\}\}",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a new dictionary with every value in <paramref name="rawParameters"/> having
|
||||||
|
/// its template variables replaced by the corresponding payload property values.
|
||||||
|
/// Pure single-variable templates (e.g. <c>{{Case.ClosedAt}}</c>) yield the raw typed
|
||||||
|
/// object; mixed or formatted templates yield a resolved string.
|
||||||
|
/// </summary>
|
||||||
|
internal static IDictionary<string, object> Resolve(
|
||||||
|
IDictionary<string, string> rawParameters,
|
||||||
|
PayloadBase payload)
|
||||||
|
{
|
||||||
|
var resolved = new Dictionary<string, object>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
F4SD-ActionConnector/Execution/QuickActionRequest.cs
Normal file
29
F4SD-ActionConnector/Execution/QuickActionRequest.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using F4SD.ActionConnector.Models;
|
||||||
|
|
||||||
|
namespace F4SD.ActionConnector.Execution
|
||||||
|
{
|
||||||
|
public sealed class QuickActionRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Matches <see cref="QuickActionDefinition.Id"/> from the configuration.
|
||||||
|
/// </summary>
|
||||||
|
public string ActionId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the Quick Action to invoke, e.g. "Document case in JIRA".
|
||||||
|
/// </summary>
|
||||||
|
public string QuickActionRef { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Template variables resolved against the event payload.
|
||||||
|
/// Values are the raw payload values; callers are responsible for casting.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<string, object> Parameters { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full context of the triggering event.
|
||||||
|
/// </summary>
|
||||||
|
public ActionContext Context { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
public sealed class ActionContext
|
public sealed class ActionContext
|
||||||
{
|
{
|
||||||
public TriggerEvent TriggerEvent { get; set; }
|
public TriggerEvent TriggerEvent { get; internal set; }
|
||||||
public bool AwaitResult { get; set; }
|
public bool AwaitResult { get; internal set; }
|
||||||
public PayloadBase Payload { get; set; }
|
public PayloadBase Payload { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
public abstract class ActionDefinitionBase
|
public abstract class ActionDefinitionBase
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; internal set; }
|
||||||
public abstract ActionType Type { get; }
|
public abstract ActionType Type { get; }
|
||||||
public bool AwaitResult { get; set; }
|
public bool AwaitResult { get; internal set; }
|
||||||
public bool IsEnabled { get; set; }
|
public bool IsEnabled { get; internal set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; internal set; }
|
||||||
public bool IsValid { get; private protected set; }
|
public bool IsValid { get; private protected set; }
|
||||||
|
|
||||||
protected ActionDefinitionBase(XmlElement xNode, cXmlParser parser)
|
protected ActionDefinitionBase(XmlElement xNode, cXmlParser parser)
|
||||||
|
|||||||
@@ -5,29 +5,29 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
public sealed class ActionResult
|
public sealed class ActionResult
|
||||||
{
|
{
|
||||||
public string ActionId { get; set; }
|
public string ActionId { get; private set; }
|
||||||
public ActionResultStatus Status { get; set; }
|
public ActionResultStatus Status { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HTTP status code for HttpCall actions; null for other types.
|
/// HTTP status code for HttpCall actions; null for other types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? HttpStatusCode { get; set; }
|
public int? HttpStatusCode { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raw response body (HttpCall) or serialized output (QuickAction).
|
/// Raw response body (HttpCall) or serialized output (QuickAction).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RawResponse { get; set; }
|
public string RawResponse { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Named fields extracted from the response via jsonPath mappings.
|
/// Named fields extracted from the response via jsonPath mappings.
|
||||||
/// Populated when DisplayInCockpit is true.
|
/// Populated when DisplayInCockpit is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, string> MappedFields { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> MappedFields { get; private set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Human-readable error message; set when Status is Failure.
|
/// Human-readable error message; set when Status is Failure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ErrorMessage { get; set; }
|
public string ErrorMessage { get; private set; }
|
||||||
|
|
||||||
public static ActionResult Ok(string actionId, string rawResponse = null)
|
public static ActionResult Ok(string actionId, string rawResponse = null)
|
||||||
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Success, RawResponse = rawResponse };
|
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Success, RawResponse = rawResponse };
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ using F4SD.ActionConnector.Enumerations;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using static C4IT.Logging.cLogManager;
|
using static C4IT.Logging.cLogManager;
|
||||||
|
|
||||||
namespace F4SD.ActionConnector.Models
|
namespace F4SD.ActionConnector.Models
|
||||||
{
|
{
|
||||||
internal class ExternalCommunicationConfiguration
|
public class ExternalCommunicationConfiguration
|
||||||
{
|
{
|
||||||
private const string FileNameConfig = "F4SD-ExternalCommunication-Configuration.xml";
|
private const string FileNameConfig = "F4SD-ExternalCommunication-Configuration.xml";
|
||||||
private const string FileNameConfigSchema = "F4SD-ExternalCommunication-Configuration.xsd";
|
private const string FileNameConfigSchema = "F4SD-ExternalCommunication-Configuration.xsd";
|
||||||
private const string ConfigRootElement = "F4SD-ExternalCommunication-Configuration";
|
public const string ConfigRootElement = "F4SD-ExternalCommunication-Configuration";
|
||||||
|
|
||||||
public IDictionary<TriggerEvent, Trigger> Triggers { get; } = new Dictionary<TriggerEvent, Trigger>();
|
public IDictionary<TriggerEvent, Trigger> Triggers { get; } = new Dictionary<TriggerEvent, Trigger>();
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
Trigger trigger = new Trigger(triggerNodeElement, parser);
|
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;
|
continue;
|
||||||
|
|
||||||
Triggers.Add(trigger.Event, trigger);
|
Triggers.Add(trigger.Event, trigger);
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
public override ActionType Type => ActionType.HttpCall;
|
public override ActionType Type => ActionType.HttpCall;
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string Url { get; internal set; }
|
||||||
public HttpMethod Method { get; set; } = HttpMethod.Post;
|
public HttpMethod Method { get; internal set; } = HttpMethod.Post;
|
||||||
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
|
public TimeSpan Timeout { get; internal set; } = TimeSpan.FromSeconds(30);
|
||||||
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> Headers { get; internal set; } = new Dictionary<string, string>();
|
||||||
public string Body { get; set; }
|
public string Body { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// jsonPath expressions keyed by field name, used to extract values from the response.
|
/// jsonPath expressions keyed by field name, used to extract values from the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary<string, string> MappedFieldJsonPaths { get; set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> MappedFieldJsonPaths { get; internal set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
internal HttpCallDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
|
internal HttpCallDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace F4SD.ActionConnector.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reference to the Quick Action to invoke, e.g. "Protocol case in JIRA".
|
/// Reference to the Quick Action to invoke, e.g. "Protocol case in JIRA".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string QuickActionRef { get; set; }
|
public string QuickActionRef { get; internal set; }
|
||||||
|
|
||||||
public IDictionary<string, string> Parameters { get; private set; } = new Dictionary<string, string>();
|
public IDictionary<string, string> Parameters { get; private set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string parameterName = cXmlParser.GetStringFromXmlAttribute(parameterNode, "name");
|
string parameterName = cXmlParser.GetStringFromXmlAttribute(parameterNode, "Name");
|
||||||
string parameterVariable = cXmlParser.GetInnerTextFromXmlElement(parameterNode);
|
string parameterVariable = cXmlParser.GetInnerTextFromXmlElement(parameterNode);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(parameterName) || string.IsNullOrEmpty(parameterVariable))
|
if (string.IsNullOrEmpty(parameterName) || string.IsNullOrEmpty(parameterVariable))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using static C4IT.Logging.cLogManager;
|
|||||||
|
|
||||||
namespace F4SD.ActionConnector.Models
|
namespace F4SD.ActionConnector.Models
|
||||||
{
|
{
|
||||||
internal class Trigger
|
public class Trigger
|
||||||
{
|
{
|
||||||
public TriggerEvent Event { get; set; }
|
public TriggerEvent Event { get; set; }
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Event = cXmlParser.GetEnumFromAttribute(xNode, "action", TriggerEvent.Unknown);
|
Event = cXmlParser.GetEnumFromAttribute(xNode, "event", TriggerEvent.Unknown);
|
||||||
|
|
||||||
XmlNode actionsNode = xNode.SelectSingleNode(ActionsNodeName);
|
XmlNode actionsNode = xNode.SelectSingleNode(ActionsNodeName);
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ namespace F4SD.ActionConnector.Models
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ActionType type = cXmlParser.GetEnumFromAttribute(actionNode, "type", ActionType.Unknown);
|
ActionType type = cXmlParser.GetEnumFromAttribute(actionNode, "Type", ActionType.Unknown);
|
||||||
|
|
||||||
ActionDefinitionBase actionDefinition = null;
|
ActionDefinitionBase actionDefinition = null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ using C4IT.FASD.Security;
|
|||||||
using C4IT.Graphics;
|
using C4IT.Graphics;
|
||||||
using C4IT.Logging;
|
using C4IT.Logging;
|
||||||
using C4IT.MultiLanguage;
|
using C4IT.MultiLanguage;
|
||||||
|
using C4IT.XML;
|
||||||
|
using F4SD.ActionConnector.Bus;
|
||||||
|
using F4SD.ActionConnector.Models;
|
||||||
using FasdDesktopUi.Basics;
|
using FasdDesktopUi.Basics;
|
||||||
using FasdDesktopUi.Basics.Helper;
|
using FasdDesktopUi.Basics.Helper;
|
||||||
using FasdDesktopUi.Basics.Models;
|
using FasdDesktopUi.Basics.Models;
|
||||||
using FasdDesktopUi.Basics.Services.Models;
|
using FasdDesktopUi.Basics.Services.Models;
|
||||||
|
using FasdDesktopUi.Basics.UiActions.NativeActions;
|
||||||
using FasdDesktopUi.Pages.CustomMessageBox;
|
using FasdDesktopUi.Pages.CustomMessageBox;
|
||||||
using FasdDesktopUi.Pages.PhoneSettingsPage;
|
using FasdDesktopUi.Pages.PhoneSettingsPage;
|
||||||
using FasdDesktopUi.Pages.SearchPage;
|
using FasdDesktopUi.Pages.SearchPage;
|
||||||
@@ -46,6 +50,8 @@ namespace FasdDesktopUi
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private ActionBus _actionBus;
|
||||||
|
|
||||||
private async void Application_Startup(object sender, StartupEventArgs e)
|
private async void Application_Startup(object sender, StartupEventArgs e)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
@@ -127,7 +133,24 @@ namespace FasdDesktopUi
|
|||||||
|
|
||||||
InitializeNotifyIcon();
|
InitializeNotifyIcon();
|
||||||
|
|
||||||
//CustomMessageBox.ShowCustomContent("Close case", enumHealthCardStateLevel.None, null, true, new CloseCaseDialogWithTicket());
|
try
|
||||||
|
{
|
||||||
|
//_actionBus = new ActionBus(new ActionBusOptions()
|
||||||
|
//{
|
||||||
|
// ConfigRootElement = cXmlParser.OpenXmlDocument(new List<cXmlFileLocation>() { 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);
|
await cAppStartUp.StartAsync(e.Args);
|
||||||
notifyIcon.Visible = true;
|
notifyIcon.Visible = true;
|
||||||
@@ -545,6 +568,8 @@ namespace FasdDesktopUi
|
|||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
|
_actionBus?.Stop();
|
||||||
|
|
||||||
if (closeUserSessionTask is ConfiguredTaskAwaitable _t)
|
if (closeUserSessionTask is ConfiguredTaskAwaitable _t)
|
||||||
await _t;
|
await _t;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using FasdDesktopUi.Basics.UiActions.NativeActions;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace FasdDesktopUi.Basics.Services.SupportCase.Processors
|
namespace FasdDesktopUi.Basics.Services.SupportCase.Processors
|
||||||
@@ -12,6 +13,7 @@ namespace FasdDesktopUi.Basics.Services.SupportCase.Processors
|
|||||||
if (!_supportCaseProccesors.ContainsKey(id))
|
if (!_supportCaseProccesors.ContainsKey(id))
|
||||||
_supportCaseProccesors.Add(id, new SupportCaseProcessor());
|
_supportCaseProccesors.Add(id, new SupportCaseProcessor());
|
||||||
|
|
||||||
|
QuickActionExecutor.SetSupportCaseProcessor(_supportCaseProccesors[id]);
|
||||||
return _supportCaseProccesors[id];
|
return _supportCaseProccesors[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Input;
|
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 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
|
namespace FasdDesktopUi.Basics
|
||||||
{
|
{
|
||||||
@@ -303,6 +304,8 @@ namespace FasdDesktopUi.Basics
|
|||||||
|
|
||||||
detailsPage?.EndPause();
|
detailsPage?.EndPause();
|
||||||
|
|
||||||
|
ActionConnectorEvents.RaiseCaseClosed(this, new CaseClosedPayload() { ClosedAt = DateTime.UtcNow });
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
tasks.Add(DirectConnectionHelper.DirectConnectionStopAsync());
|
tasks.Add(DirectConnectionHelper.DirectConnectionStopAsync());
|
||||||
|
|
||||||
|
|||||||
@@ -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<ActionResult> 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<cAdjustableParameter, object> GetParameterDictionaryFrom(IDictionary<string, object> requestParameters)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return requestParameters.ToDictionary(p => new cAdjustableParameterString() { ParameterName = p.Key } as cAdjustableParameter, p => p.Value);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogException(ex);
|
||||||
|
return new Dictionary<cAdjustableParameter, object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,7 +137,7 @@ namespace FasdDesktopUi.Basics.UiActions
|
|||||||
quickActionOutput = new cQuickActionOutputSingle(new cF4sdQuickActionRevision.cOutput() { ResultCode = enumQuickActionSuccess.finished });
|
quickActionOutput = new cQuickActionOutputSingle(new cF4sdQuickActionRevision.cOutput() { ResultCode = enumQuickActionSuccess.finished });
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StatusMonitor.QuickActionOutputs.Add(ResultRevision.Output);
|
StatusMonitor?.QuickActionOutputs?.Add(ResultRevision.Output);
|
||||||
quickActionOutput = cQuickActionOutput.GetQuickActionOutput(ResultRevision.Output, DataProvider);
|
quickActionOutput = cQuickActionOutput.GetQuickActionOutput(ResultRevision.Output, DataProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ namespace FasdDesktopUi.Basics.UiActions
|
|||||||
cMultiLanguageSupport.CurrentLanguage = tempLang;
|
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);
|
QuickActionProtocollEntry protocollEntry = QuickActionProtocollEntryOutput.GetQuickActionProtocollEntry(LocalQuickAction, copyData);
|
||||||
|
|
||||||
F4SDProtocoll.Instance.Add(protocollEntry);
|
F4SDProtocoll.Instance.Add(protocollEntry);
|
||||||
@@ -192,7 +192,7 @@ namespace FasdDesktopUi.Basics.UiActions
|
|||||||
cMultiLanguageSupport.CurrentLanguage = tempLang;
|
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);
|
QuickActionProtocollEntry protocollEntry = QuickActionProtocollEntryOutput.GetQuickActionProtocollEntry(LocalQuickAction, copyData);
|
||||||
|
|
||||||
F4SDProtocoll.Instance.Add(protocollEntry);
|
F4SDProtocoll.Instance.Add(protocollEntry);
|
||||||
|
|||||||
@@ -152,8 +152,7 @@
|
|||||||
Background="{DynamicResource BackgroundColor.DetailsPage.Widget.Title}"
|
Background="{DynamicResource BackgroundColor.DetailsPage.Widget.Title}"
|
||||||
SpellCheck.IsEnabled="True"
|
SpellCheck.IsEnabled="True"
|
||||||
SelectionChanged="NotepadRichTextBox_SelectionChanged"
|
SelectionChanged="NotepadRichTextBox_SelectionChanged"
|
||||||
IsVisibleChanged="NotepadRichTextBox_IsVisibleChanged"
|
IsVisibleChanged="NotepadRichTextBox_IsVisibleChanged">
|
||||||
GotKeyboardFocus="NotepadRichTextBox_GotKeyboardFocus">
|
|
||||||
<RichTextBox.Resources>
|
<RichTextBox.Resources>
|
||||||
<Style TargetType="Border">
|
<Style TargetType="Border">
|
||||||
<Setter Property="CornerRadius"
|
<Setter Property="CornerRadius"
|
||||||
|
|||||||
@@ -155,29 +155,6 @@ namespace FasdDesktopUi.Basics.UserControls
|
|||||||
|
|
||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
|
|
||||||
private void NotepadRichTextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_dataProvider is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_dataProvider?.CaseNotes?.Parent is RichTextBox parentBox)
|
|
||||||
parentBox.Document = new FlowDocument();
|
|
||||||
|
|
||||||
if (!(sender is RichTextBox senderElement))
|
|
||||||
return;
|
|
||||||
|
|
||||||
senderElement.Document = _dataProvider.CaseNotes;
|
|
||||||
senderElement.CaretPosition = senderElement.CaretPosition.DocumentEnd;
|
|
||||||
}
|
|
||||||
catch (Exception E)
|
|
||||||
{
|
|
||||||
LogException(E);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void NotepadRichTextBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
private async void NotepadRichTextBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -185,11 +162,15 @@ namespace FasdDesktopUi.Basics.UserControls
|
|||||||
if (!(e.NewValue is bool isVisible && isVisible))
|
if (!(e.NewValue is bool isVisible && isVisible))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Task.Delay(50);
|
if (!(sender is RichTextBox senderElement))
|
||||||
NotepadRichTextBox.Focus();
|
return;
|
||||||
Keyboard.Focus(NotepadRichTextBox);
|
|
||||||
|
|
||||||
NotepadRichTextBox_GotKeyboardFocus(sender, null);
|
senderElement.Document = _dataProvider.CaseNotes;
|
||||||
|
senderElement.CaretPosition = senderElement.CaretPosition.DocumentEnd;
|
||||||
|
|
||||||
|
await Task.Delay(50);
|
||||||
|
senderElement.Focus();
|
||||||
|
Keyboard.Focus(senderElement);
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -252,6 +252,7 @@
|
|||||||
<Compile Include="Basics\PrivateSecurePassword.cs" />
|
<Compile Include="Basics\PrivateSecurePassword.cs" />
|
||||||
<Compile Include="Basics\UiActions\ChangeHealthCardAction.cs" />
|
<Compile Include="Basics\UiActions\ChangeHealthCardAction.cs" />
|
||||||
<Compile Include="Basics\UiActions\CopyQuickActionProtocolAction.cs" />
|
<Compile Include="Basics\UiActions\CopyQuickActionProtocolAction.cs" />
|
||||||
|
<Compile Include="Basics\UiActions\NativeActions\QuickActionExecutor.cs" />
|
||||||
<Compile Include="Basics\UiActions\NativeActions\RemoteConnectionAction.cs" />
|
<Compile Include="Basics\UiActions\NativeActions\RemoteConnectionAction.cs" />
|
||||||
<Compile Include="Basics\UiActions\UiCopyDetailsTableContent.cs" />
|
<Compile Include="Basics\UiActions\UiCopyDetailsTableContent.cs" />
|
||||||
<Compile Include="Basics\UiActions\UiDummyQuickAction.cs" />
|
<Compile Include="Basics\UiActions\UiDummyQuickAction.cs" />
|
||||||
@@ -1017,6 +1018,10 @@
|
|||||||
<EmbeddedResource Include="Config\LanguageDefinitions.xml" />
|
<EmbeddedResource Include="Config\LanguageDefinitions.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\F4SD-ActionConnector\F4SD-ActionConnector.csproj">
|
||||||
|
<Project>{887c6721-b9f3-c83a-3d55-5e684ca19592}</Project>
|
||||||
|
<Name>F4SD-ActionConnector</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\F4SD-AdaptableIcon\F4SD-AdaptableIcon.csproj">
|
<ProjectReference Include="..\F4SD-AdaptableIcon\F4SD-AdaptableIcon.csproj">
|
||||||
<Project>{bab63a6a-1524-435d-9f96-7a30b6ee0624}</Project>
|
<Project>{bab63a6a-1524-435d-9f96-7a30b6ee0624}</Project>
|
||||||
<Name>F4SD-AdaptableIcon</Name>
|
<Name>F4SD-AdaptableIcon</Name>
|
||||||
|
|||||||
Reference in New Issue
Block a user