inital
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.Models
|
||||
{
|
||||
public sealed class TicketOverviewCountsChangedEventArgs : EventArgs
|
||||
{
|
||||
public TicketOverviewCountsChangedEventArgs(IReadOnlyList<TileCountChange> changes, IReadOnlyDictionary<string, TileCounts> currentCounts)
|
||||
{
|
||||
Changes = changes;
|
||||
CurrentCounts = currentCounts;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TileCountChange> Changes { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TileCounts> CurrentCounts { get; }
|
||||
}
|
||||
|
||||
public readonly struct TileCountChange
|
||||
{
|
||||
public TileCountChange(string key, TileScope scope, int oldCount, int newCount)
|
||||
{
|
||||
Key = key;
|
||||
Scope = scope;
|
||||
OldCount = oldCount;
|
||||
NewCount = newCount;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public TileScope Scope { get; }
|
||||
|
||||
public int OldCount { get; }
|
||||
|
||||
public int NewCount { get; }
|
||||
|
||||
public int Delta => NewCount - OldCount;
|
||||
}
|
||||
|
||||
public readonly struct TileCounts
|
||||
{
|
||||
public static TileCounts Empty => new TileCounts(0, 0);
|
||||
|
||||
public TileCounts(int personal, int role)
|
||||
{
|
||||
Personal = personal;
|
||||
Role = role;
|
||||
}
|
||||
|
||||
public int Personal { get; }
|
||||
|
||||
public int Role { get; }
|
||||
}
|
||||
|
||||
public enum TileScope
|
||||
{
|
||||
Personal,
|
||||
Role
|
||||
}
|
||||
}
|
||||
109
FasdDesktopUi/Basics/Services/ProtocollService/F4SDProtocoll.cs
Normal file
109
FasdDesktopUi/Basics/Services/ProtocollService/F4SDProtocoll.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.ProtocollService
|
||||
{
|
||||
internal class F4SDProtocoll
|
||||
{
|
||||
private readonly ICollection<IProtocollEntry> _protocollEntries = new List<IProtocollEntry>();
|
||||
|
||||
public static F4SDProtocoll Instance { get; private set; } = new F4SDProtocoll();
|
||||
|
||||
private F4SDProtocoll() { }
|
||||
|
||||
internal void Add(IProtocollEntry entry) => _protocollEntries.Add(entry);
|
||||
internal void Add(IEnumerable<IProtocollEntry> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Clear() => _protocollEntries.Clear();
|
||||
|
||||
internal T GetLatestOfType<T>() where T : IProtocollEntry => _protocollEntries.OfType<T>().LastOrDefault();
|
||||
|
||||
internal IEnumerable<T> GetOfType<T>(int? count = null) where T : IProtocollEntry
|
||||
{
|
||||
if(count.HasValue)
|
||||
return _protocollEntries.OfType<T>().Reverse().Take(count.Value).Reverse();
|
||||
return _protocollEntries.OfType<T>();
|
||||
}
|
||||
|
||||
internal IEnumerable<IProtocollEntry> GetAll() => _protocollEntries;
|
||||
|
||||
internal DataObject GetLatestOfTypeAsDataObject<T>(bool skipHtmlFrame) where T : IProtocollEntry
|
||||
{
|
||||
IProtocollEntry entry = _protocollEntries.OfType<T>().LastOrDefault();
|
||||
|
||||
if (entry is null)
|
||||
return new DataObject();
|
||||
|
||||
return GetDataObjectOf(new IProtocollEntry[] { entry }, skipHtmlFrame);
|
||||
}
|
||||
|
||||
internal DataObject GetOfTypeAsDataObject<T>(bool skipHtmlFrame, int? count = null) where T : IProtocollEntry
|
||||
=> GetDataObjectOf(GetOfType<T>(count).Cast<IProtocollEntry>(), skipHtmlFrame);
|
||||
|
||||
internal DataObject GetAllAsDataObject(bool skipHtmlFrame)
|
||||
=> GetDataObjectOf(_protocollEntries, skipHtmlFrame);
|
||||
|
||||
private DataObject GetDataObjectOf(IEnumerable<IProtocollEntry> entries, bool skipHtmlFrame, int? count = null)
|
||||
{
|
||||
DataObject dataObject = new DataObject();
|
||||
|
||||
const string asciiSeparator = "\n\n\n";
|
||||
const string htmlSeparator = "<br/><hr/><br/>";
|
||||
|
||||
try
|
||||
{
|
||||
string ascii = string.Empty;
|
||||
string html = string.Empty;
|
||||
|
||||
if (count.HasValue)
|
||||
entries = entries.Reverse().Take(count.Value).Reverse();
|
||||
|
||||
foreach (IProtocollEntry entry in entries)
|
||||
{
|
||||
string entryAscii = entry.GetAscii();
|
||||
if (!string.IsNullOrEmpty(entryAscii))
|
||||
{
|
||||
ascii += entryAscii;
|
||||
ascii += asciiSeparator;
|
||||
}
|
||||
|
||||
string entryHtml = entry.GetHtml();
|
||||
if (!string.IsNullOrEmpty(entryHtml))
|
||||
{
|
||||
html += entryHtml;
|
||||
html += htmlSeparator;
|
||||
}
|
||||
}
|
||||
|
||||
if (ascii.EndsWith(asciiSeparator))
|
||||
ascii = ascii.Remove(ascii.Length - asciiSeparator.Length);
|
||||
|
||||
if (html.EndsWith(htmlSeparator))
|
||||
html = html.Remove(html.Length - htmlSeparator.Length);
|
||||
|
||||
dataObject.SetText(ascii);
|
||||
|
||||
if (!string.IsNullOrEmpty(html))
|
||||
{
|
||||
html = skipHtmlFrame ? html : cUtility.GetHtmlFrame(html);
|
||||
dataObject.SetText(html, TextDataFormat.Html);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return dataObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.ProtocollService
|
||||
{
|
||||
internal interface IProtocollEntry
|
||||
{
|
||||
string GetAscii();
|
||||
string GetHtml();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
using C4IT.F4SD.DisplayFormatting;
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.MultiLanguage;
|
||||
using FasdDesktopUi.Basics.Models;
|
||||
using FasdDesktopUi.Basics.UserControls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
using static FasdDesktopUi.Basics.UserControls.QuickActionStatusMonitor;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.ProtocollService
|
||||
{
|
||||
internal class QuickActionProtocollEntry : IProtocollEntry
|
||||
{
|
||||
private readonly cFasdQuickAction _quickActionDefinition;
|
||||
private readonly cQuickActionCopyData _quickActionCopyData;
|
||||
private readonly IRawValueFormatter _rawValueFormatter = new RawValueFormatter();
|
||||
|
||||
const string AsciiSeperator = "\n\n";
|
||||
|
||||
public QuickActionProtocollEntry(cFasdQuickAction quickActionDefinition, cQuickActionCopyData quickActionCopyData)
|
||||
{
|
||||
_quickActionDefinition = quickActionDefinition;
|
||||
_quickActionCopyData = quickActionCopyData;
|
||||
}
|
||||
|
||||
internal static cQuickActionCopyData GetCopyData(cFasdQuickAction quickActionDefinition, cSupportCaseDataProvider dataProvider, bool wasRunningOnAffectedDevice, cQuickActionOutput quickActionOutput, List<cQuickActionMeasureValue> measureValues)
|
||||
{
|
||||
cQuickActionCopyData quickActionCopyData = new cQuickActionCopyData();
|
||||
try
|
||||
{
|
||||
string currentLanguage = cMultiLanguageSupport.CurrentLanguage;
|
||||
cMultiLanguageSupport.CurrentLanguage = cF4SDCockpitXmlConfig.Instance.HealthCardConfig.ProtocollLanguage ?? currentLanguage;
|
||||
|
||||
quickActionCopyData.Name = quickActionDefinition.Names.GetValue();
|
||||
quickActionCopyData.ExecutionTime = DateTime.UtcNow;
|
||||
|
||||
if (dataProvider.HealthCardDataHelper.HeadingData.TryGetValue(enumFasdInformationClass.Computer, out var computerHeadingData))
|
||||
quickActionCopyData.AffectedDeviceName = computerHeadingData.HeadingText;
|
||||
|
||||
quickActionCopyData.WasRunningOnAffectedDevice = wasRunningOnAffectedDevice;
|
||||
quickActionCopyData.QuickActionOutput = quickActionOutput;
|
||||
quickActionCopyData.MeasureValues = measureValues;
|
||||
|
||||
cMultiLanguageSupport.CurrentLanguage = currentLanguage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return quickActionCopyData;
|
||||
}
|
||||
|
||||
internal cQuickActionResult GetResult()
|
||||
{
|
||||
return new cQuickActionResult()
|
||||
{
|
||||
QuickActionId = _quickActionDefinition.Id,
|
||||
QuickActionName = _quickActionDefinition.Name,
|
||||
QuickActionExecutionType = (int)_quickActionDefinition.ExecutionType,
|
||||
wasRunningOnAffectedDevice = _quickActionCopyData.WasRunningOnAffectedDevice,
|
||||
AffectedDeviceName = _quickActionCopyData.AffectedDeviceName,
|
||||
ExecutionTime = DateTime.UtcNow,
|
||||
ResultCode = (int?)(_quickActionCopyData.QuickActionOutput?.ResultCode),
|
||||
ErrorMessage = _quickActionCopyData.QuickActionOutput?.ErrorDescription,
|
||||
Output = GetQuickActionHtmlOutput(_quickActionCopyData.QuickActionOutput),
|
||||
MeasureValues = GetQuickActionHtmlValueComparison(_quickActionCopyData.MeasureValues)
|
||||
};
|
||||
}
|
||||
|
||||
public string GetAscii() => GetQuickActionAscii(_quickActionCopyData);
|
||||
|
||||
public string GetHtml() => GetQuickActionHtml(_quickActionCopyData);
|
||||
|
||||
private bool ShouldHideQuickActionOutput(string outputValueKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_quickActionDefinition.ColumnOutputFormattings is null)
|
||||
return false;
|
||||
|
||||
if (!_quickActionDefinition.ShowAllOutputContent && !_quickActionDefinition.ColumnOutputFormattings.ContainsKey(outputValueKey))
|
||||
return true;
|
||||
|
||||
if (_quickActionDefinition.ColumnOutputFormattings.TryGetValue(outputValueKey, out var columnFormatting))
|
||||
return columnFormatting.Hidden;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Ascii
|
||||
|
||||
private string GetQuickActionAscii(cQuickActionCopyData copyData)
|
||||
{
|
||||
string ascii = string.Empty;
|
||||
|
||||
ascii += GetQuickActionAsciiDescription(copyData.Name, copyData.AffectedDeviceName, copyData.WasRunningOnAffectedDevice, copyData.ExecutionTime, copyData.QuickActionOutput?.ResultCode);
|
||||
ascii += GetQuickActionAsciiError(copyData.QuickActionOutput?.ErrorDescription);
|
||||
ascii += GetQuickActionAsciiOutput(copyData.QuickActionOutput);
|
||||
ascii += GetQuickActionAsciiValueComparisonString(copyData.MeasureValues);
|
||||
|
||||
return ascii;
|
||||
}
|
||||
|
||||
private string GetQuickActionAsciiDescription(string quickActionName, string deviceName, bool wasRunningOnAffectedDevice, DateTime executionTime, enumQuickActionSuccess? quickActionStatus)
|
||||
{
|
||||
string asciiDescription = string.Empty;
|
||||
try
|
||||
{
|
||||
var quickActionStatusString = string.Empty;
|
||||
|
||||
switch (quickActionStatus)
|
||||
{
|
||||
case null:
|
||||
case enumQuickActionSuccess.successfull:
|
||||
quickActionStatusString = cMultiLanguageSupport.GetItem("QuickAction.Copy.RevisionStatus.Successfull");
|
||||
break;
|
||||
case enumQuickActionSuccess.error:
|
||||
quickActionStatusString = cMultiLanguageSupport.GetItem("QuickAction.Copy.RevisionStatus.Error");
|
||||
break;
|
||||
}
|
||||
|
||||
var rawDescription = wasRunningOnAffectedDevice ? cMultiLanguageSupport.GetItem("QuickAction.Remote.Copy.Description") : cMultiLanguageSupport.GetItem("QuickAction.Local.Copy.Description");
|
||||
asciiDescription = string.Format(rawDescription, quickActionName, deviceName, executionTime.ToString("g", new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage)), quickActionStatusString);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return asciiDescription;
|
||||
}
|
||||
|
||||
private string GetQuickActionAsciiError(string errorMessage)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
errorMessage.Insert(0, AsciiSeperator);
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
private string GetQuickActionAsciiOutput(QuickActionStatusMonitor.cQuickActionOutput quickActionOutput)
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(quickActionOutput?.ErrorDescription))
|
||||
return output;
|
||||
|
||||
switch (quickActionOutput)
|
||||
{
|
||||
case QuickActionStatusMonitor.cQuickActionOutputSingle singleOutput:
|
||||
{
|
||||
if (singleOutput.Value is null)
|
||||
return output;
|
||||
|
||||
output += AsciiSeperator;
|
||||
output += cMultiLanguageSupport.GetItem("QuickAction.Copy.Output") + " ";
|
||||
output += singleOutput.GetDisplayValue(_quickActionDefinition.ColumnOutputFormattings);
|
||||
|
||||
break;
|
||||
}
|
||||
case QuickActionStatusMonitor.cQuickActionOutputList listOutput:
|
||||
{
|
||||
output += AsciiSeperator;
|
||||
output += cMultiLanguageSupport.GetItem("QuickAction.Copy.Output") + "\n";
|
||||
|
||||
|
||||
foreach (var value in listOutput.Values[0])
|
||||
{
|
||||
if (ShouldHideQuickActionOutput(value.Key))
|
||||
continue;
|
||||
|
||||
if (_quickActionDefinition.ColumnOutputFormattings?.TryGetValue(value.Key, out var outputFormatting) ?? false)
|
||||
output += outputFormatting.Names.GetValue();
|
||||
else
|
||||
output += value.Key;
|
||||
|
||||
output += " | ";
|
||||
}
|
||||
|
||||
for (int i = 0; i < listOutput.Values.Count; i++)
|
||||
{
|
||||
output += "\n";
|
||||
|
||||
for (int j = 0; j < listOutput.Values[i].Count; j++)
|
||||
{
|
||||
string valueKey = listOutput.Values[0][j].Key;
|
||||
|
||||
if (ShouldHideQuickActionOutput(valueKey))
|
||||
continue;
|
||||
|
||||
string displayValue = listOutput.GetDisplayValue(i, j, _quickActionDefinition.ColumnOutputFormattings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(displayValue))
|
||||
continue;
|
||||
|
||||
output += displayValue;
|
||||
output += " | ";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case QuickActionStatusMonitor.cQuickActionOutputObject objectOutput:
|
||||
{
|
||||
output += AsciiSeperator;
|
||||
output += cMultiLanguageSupport.GetItem("QuickAction.Copy.Output") + "\n";
|
||||
|
||||
for (int i = 0; i < objectOutput.Values.Count; i++)
|
||||
{
|
||||
var value = objectOutput.Values[i];
|
||||
|
||||
if (ShouldHideQuickActionOutput(value.Key))
|
||||
continue;
|
||||
|
||||
string columnTitle = string.Empty;
|
||||
|
||||
if (_quickActionDefinition.ColumnOutputFormattings?.TryGetValue(value.Key, out var outputFormatting) ?? false)
|
||||
columnTitle = outputFormatting.Names.GetValue();
|
||||
else
|
||||
columnTitle = value.Key;
|
||||
|
||||
if (!string.IsNullOrEmpty(columnTitle))
|
||||
output += $"{columnTitle}: ";
|
||||
|
||||
string displayValue = objectOutput.GetDisplayValue(i, _quickActionDefinition.ColumnOutputFormattings);
|
||||
output += !string.IsNullOrWhiteSpace(displayValue) ? displayValue : null;
|
||||
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private string GetQuickActionAsciiValueComparisonString(List<QuickActionStatusMonitor.cQuickActionMeasureValue> measureValues)
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (measureValues is null || measureValues.Count <= 0)
|
||||
return output;
|
||||
|
||||
output += AsciiSeperator + cMultiLanguageSupport.GetItem("QuickAction.Copy.Measure");
|
||||
|
||||
_rawValueFormatter.SetDefaultCulture(new System.Globalization.CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage));
|
||||
|
||||
foreach (var measureValue in measureValues)
|
||||
{
|
||||
try
|
||||
{
|
||||
string value = _rawValueFormatter.GetDisplayValue(measureValue.Value, measureValue.Display);
|
||||
string postValue = _rawValueFormatter.GetDisplayValue(measureValue.PostValue, measureValue.Display);
|
||||
string difference = string.Empty;
|
||||
|
||||
if (measureValue.Difference != null)
|
||||
difference = $" (∆ {_rawValueFormatter.GetDisplayValue(measureValue.Difference, measureValue.Display)})";
|
||||
|
||||
output += "\n" + measureValue.Names.GetValue(cF4SDCockpitXmlConfig.Instance.HealthCardConfig.ProtocollLanguage) + ": " + value + " ➜ " + postValue + difference;
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Html
|
||||
|
||||
private string GetQuickActionHtml(cQuickActionCopyData copyData)
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
output += GetQuickActionHtmlDescription(copyData.Name, copyData.AffectedDeviceName, copyData.WasRunningOnAffectedDevice, copyData.ExecutionTime, copyData.QuickActionOutput?.ResultCode);
|
||||
output += GetQuickActionHtmlError(copyData.QuickActionOutput?.ErrorDescription);
|
||||
output += GetQuickActionHtmlOutput(copyData.QuickActionOutput);
|
||||
output += GetQuickActionHtmlValueComparison(copyData.MeasureValues);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private string GetQuickActionHtmlDescription(string quickActionName, string deviceName, bool wasRunningOnAffectedDevice, DateTime executionTime, enumQuickActionSuccess? quickActionStatus)
|
||||
{
|
||||
string output = string.Empty;
|
||||
try
|
||||
{
|
||||
var quickActionStatusString = string.Empty;
|
||||
|
||||
switch (quickActionStatus)
|
||||
{
|
||||
case null:
|
||||
case enumQuickActionSuccess.successfull:
|
||||
quickActionStatusString = cMultiLanguageSupport.GetItem("QuickAction.Copy.RevisionStatus.Successfull");
|
||||
break;
|
||||
case enumQuickActionSuccess.error:
|
||||
quickActionStatusString = cMultiLanguageSupport.GetItem("QuickAction.Copy.RevisionStatus.Error");
|
||||
break;
|
||||
}
|
||||
|
||||
var rawDescription = wasRunningOnAffectedDevice ? cMultiLanguageSupport.GetItem("QuickAction.Remote.Copy.Description.Html") : cMultiLanguageSupport.GetItem("QuickAction.Local.Copy.Description.Html");
|
||||
output = string.Format(rawDescription, quickActionName, deviceName, executionTime.ToString("g", new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage)), quickActionStatusString);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static string GetQuickActionHtmlError(string errorMessage)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
errorMessage = $"<br/><p style=\"color:red\">{errorMessage}</p>";
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
private string GetQuickActionHtmlOutput(QuickActionStatusMonitor.cQuickActionOutput quickActionOutput)
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(quickActionOutput?.ErrorDescription))
|
||||
return output;
|
||||
|
||||
output += "<br/>";
|
||||
|
||||
switch (quickActionOutput)
|
||||
{
|
||||
case QuickActionStatusMonitor.cQuickActionOutputSingle singleOutput:
|
||||
{
|
||||
if (singleOutput.Value is null)
|
||||
return output;
|
||||
|
||||
var displayValue = singleOutput.GetDisplayValue(_quickActionDefinition?.ColumnOutputFormattings);
|
||||
output += "<p>" + cMultiLanguageSupport.GetItem("QuickAction.Copy.Output.Html") + " " + displayValue + "</p>";
|
||||
break;
|
||||
}
|
||||
case QuickActionStatusMonitor.cQuickActionOutputList listOutput:
|
||||
{
|
||||
output += "<p>" + cMultiLanguageSupport.GetItem("QuickAction.Copy.Output.Html") + "</p>";
|
||||
|
||||
output += "<table border=\"1\">";
|
||||
output += "<tr>";
|
||||
|
||||
foreach (var value in listOutput.Values[0])
|
||||
{
|
||||
if (ShouldHideQuickActionOutput(value.Key))
|
||||
continue;
|
||||
|
||||
string headingValue = value.Key;
|
||||
if (_quickActionDefinition.ColumnOutputFormattings?.TryGetValue(value.Key, out var outputFormatting) ?? false)
|
||||
headingValue = outputFormatting.Names.GetValue();
|
||||
|
||||
output += "<th align=\"left\">";
|
||||
output += headingValue;
|
||||
output += "</th>";
|
||||
}
|
||||
|
||||
output += "</tr>";
|
||||
|
||||
for (int i = 0; i < listOutput.Values.Count; i++)
|
||||
{
|
||||
output += "<tr>";
|
||||
|
||||
for (int j = 0; j < listOutput.Values[i].Count; j++)
|
||||
{
|
||||
string valueKey = listOutput.Values[0][j].Key;
|
||||
if (ShouldHideQuickActionOutput(valueKey))
|
||||
continue;
|
||||
|
||||
string displayValue = listOutput.GetDisplayValue(i, j, _quickActionDefinition.ColumnOutputFormattings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(displayValue))
|
||||
continue;
|
||||
|
||||
output += "<td>";
|
||||
output += displayValue;
|
||||
output += "</td>";
|
||||
}
|
||||
|
||||
output += "</tr>";
|
||||
}
|
||||
|
||||
output += "</table>";
|
||||
break;
|
||||
}
|
||||
case QuickActionStatusMonitor.cQuickActionOutputObject objectOutput:
|
||||
{
|
||||
output += "<p>" + cMultiLanguageSupport.GetItem("QuickAction.Copy.Output.Html") + "</p>";
|
||||
|
||||
output += "<table border=\"1\">";
|
||||
|
||||
for (int i = 0; i < objectOutput.Values.Count; i++)
|
||||
{
|
||||
var value = objectOutput.Values[i];
|
||||
|
||||
if (ShouldHideQuickActionOutput(value.Key))
|
||||
continue;
|
||||
|
||||
string headingValue = value.Key;
|
||||
if (_quickActionDefinition.ColumnOutputFormattings?.TryGetValue(value.Key, out var outputFormatting) ?? false)
|
||||
headingValue = outputFormatting.Names.GetValue();
|
||||
|
||||
output += "<tr>";
|
||||
output += "<td>";
|
||||
output += headingValue;
|
||||
output += "</td>";
|
||||
|
||||
output += "<td>";
|
||||
string displayValue = objectOutput.GetDisplayValue(i, _quickActionDefinition.ColumnOutputFormattings);
|
||||
output += !string.IsNullOrWhiteSpace(displayValue) ? displayValue : null;
|
||||
output += "</td>";
|
||||
|
||||
output += "</tr>";
|
||||
}
|
||||
|
||||
output += "</table>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private string GetQuickActionHtmlValueComparison(List<QuickActionStatusMonitor.cQuickActionMeasureValue> measureValues)
|
||||
{
|
||||
string output = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
if (measureValues is null || measureValues.Count <= 0)
|
||||
return output;
|
||||
|
||||
output += "<p>" + cMultiLanguageSupport.GetItem("QuickAction.Copy.Measure.Html") + "</p>";
|
||||
|
||||
_rawValueFormatter.SetDefaultCulture(new System.Globalization.CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage));
|
||||
|
||||
foreach (var measureValue in measureValues)
|
||||
{
|
||||
try
|
||||
{
|
||||
string value = _rawValueFormatter.GetDisplayValue(measureValue.Value, measureValue.Display);
|
||||
string postValue = _rawValueFormatter.GetDisplayValue(measureValue.PostValue, measureValue.Display);
|
||||
string difference = string.Empty;
|
||||
|
||||
if (measureValue.Difference != null)
|
||||
difference = $" (∆ {_rawValueFormatter.GetDisplayValue(measureValue.Difference, measureValue.Display)})";
|
||||
|
||||
output += "<p>";
|
||||
output += "<b>" + measureValue.Names.GetValue(cF4SDCockpitXmlConfig.Instance.HealthCardConfig.ProtocollLanguage) + ": </b>" + value + " ➜ " + postValue + difference;
|
||||
output += "</p>";
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.MultiLanguage;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.ProtocollService
|
||||
{
|
||||
internal class QuickTipStepProtocollEntry : IProtocollEntry
|
||||
{
|
||||
private readonly cQuickTipElement _quickTipElementDefinition;
|
||||
private readonly bool _wasSuccessfull;
|
||||
|
||||
public QuickTipStepProtocollEntry(cQuickTipElement quickTipElementDefinition, bool wasSuccessfull)
|
||||
{
|
||||
_quickTipElementDefinition = quickTipElementDefinition;
|
||||
_wasSuccessfull = wasSuccessfull;
|
||||
}
|
||||
|
||||
public string GetAscii()
|
||||
{
|
||||
string currentLanguage = cMultiLanguageSupport.CurrentLanguage;
|
||||
try
|
||||
{
|
||||
cMultiLanguageSupport.CurrentLanguage = cF4SDCockpitXmlConfig.Instance.HealthCardConfig.ProtocollLanguage ?? currentLanguage;
|
||||
string ascii = _wasSuccessfull ? cMultiLanguageSupport.GetItem("QuickTips.Copy.ManualStep.Successfull") : cMultiLanguageSupport.GetItem("QuickTips.Copy.ManualStep.Unsuccessfull");
|
||||
return string.Format(ascii, _quickTipElementDefinition?.Names?.GetValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
cMultiLanguageSupport.CurrentLanguage = currentLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetHtml()
|
||||
=> GetAscii();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FasdDesktopUi.Basics.Services.ProtocollService
|
||||
{
|
||||
internal class TextualProtocollEntry : IProtocollEntry
|
||||
{
|
||||
private readonly string _asciiText;
|
||||
private readonly string _htmlText;
|
||||
|
||||
public TextualProtocollEntry(string asciiText, string htmlText)
|
||||
{
|
||||
_asciiText = asciiText;
|
||||
_htmlText = htmlText;
|
||||
}
|
||||
|
||||
public string GetAscii() => _asciiText;
|
||||
|
||||
public string GetHtml() => _htmlText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using C4IT.FASD.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.RelationService
|
||||
{
|
||||
public interface IRelationService
|
||||
{
|
||||
event EventHandler<StagedSearchResultRelationsEventArgs> RelationsFound;
|
||||
|
||||
IEnumerable<cF4sdApiSearchResultRelation> GetLoadedRelations();
|
||||
Task<cF4sdStagedSearchResultRelationTaskId> LoadRelationsAsync(IEnumerable<cFasdApiSearchResultEntry> relatedTo, CancellationToken token = default);
|
||||
IRelationService Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using C4IT.FASD.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.RelationService
|
||||
{
|
||||
public class RelationEventArgs : EventArgs
|
||||
{
|
||||
public ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> Relations { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.FASD.Cockpit.Communication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
|
||||
[assembly: InternalsVisibleTo("F4SD.Cockpit.Client.Test")]
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.RelationService
|
||||
{
|
||||
internal class RelationService : IRelationService
|
||||
{
|
||||
private IEnumerable<cF4sdApiSearchResultRelation> _relations = new List<cF4sdApiSearchResultRelation>();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously loads relations for the specified search results.
|
||||
/// </summary>
|
||||
/// <remarks>This method initiates a task to gather relations for the provided search results and
|
||||
/// periodically checks for the completion of the task. Once relations are retrieved, an event <see cref="RelationsFound"/>
|
||||
/// is raised to notify listeners of the progress.</remarks>
|
||||
/// <returns>Task id and pending information classes</returns>
|
||||
public async Task<cF4sdStagedSearchResultRelationTaskId> LoadRelationsAsync(IEnumerable<cFasdApiSearchResultEntry> relatedTo, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_relations = new List<cF4sdApiSearchResultRelation>();
|
||||
cF4sdStagedSearchResultRelationTaskId gatherRelationTask = await cFasdCockpitCommunicationBase.Instance.StartGatheringRelations(relatedTo, token);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
const int maxRetryCount = 10;
|
||||
for (int i = 0; i < maxRetryCount; i++)
|
||||
{
|
||||
cF4sdStagedSearchResultRelations stagedRelations = await cFasdCockpitCommunicationBase.Instance.GetStagedRelations(gatherRelationTask.Id, token);
|
||||
stagedRelations.MergeAsRelationInfosWith(relatedTo);
|
||||
|
||||
_relations = _relations.Union(stagedRelations.Relations);
|
||||
RelationsFound?.Invoke(this, new StagedSearchResultRelationsEventArgs() { RelatedTo = relatedTo, StagedResultRelations = stagedRelations });
|
||||
|
||||
if (stagedRelations?.IsComplete ?? false)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return gatherRelationTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<cF4sdApiSearchResultRelation> GetLoadedRelations() => _relations;
|
||||
|
||||
public IRelationService Clone()
|
||||
{
|
||||
RelationService copy = (RelationService)MemberwiseClone();
|
||||
copy._relations = _relations.Select(r => r).ToList();
|
||||
return copy;
|
||||
}
|
||||
|
||||
public event EventHandler<StagedSearchResultRelationsEventArgs> RelationsFound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using C4IT.FASD.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.RelationService
|
||||
{
|
||||
public class StagedSearchResultRelationsEventArgs : EventArgs
|
||||
{
|
||||
public IEnumerable<cFasdApiSearchResultEntry> RelatedTo { get; set; }
|
||||
public cF4sdStagedSearchResultRelations StagedResultRelations { get; set; }
|
||||
public IRelationService RelationService { get; set; }
|
||||
}
|
||||
}
|
||||
21
FasdDesktopUi/Basics/Services/SupportCase/ISupportCase.cs
Normal file
21
FasdDesktopUi/Basics/Services/SupportCase/ISupportCase.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using C4IT.FASD.Base;
|
||||
using FasdDesktopUi.Basics.Services.RelationService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.SupportCase
|
||||
{
|
||||
public interface ISupportCase
|
||||
{
|
||||
cSupportCaseDataProvider SupportCaseDataProviderArtifact { get; }
|
||||
void Initialize();
|
||||
void AddCaseRelations(ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> relations);
|
||||
ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> GetCaseRelations();
|
||||
IEnumerable<cF4SDHealthCardRawData.cHealthCardTable> GetSupportCaseHealthcardData(object identities, object valueAddress);
|
||||
void UpdateSupportCaseDataCache();
|
||||
|
||||
event EventHandler<RelationEventArgs> CaseRelationsAdded;
|
||||
event EventHandler<object> SupportCaseDataCacheHasChanged;
|
||||
}
|
||||
}
|
||||
125
FasdDesktopUi/Basics/Services/SupportCase/SupportCase.cs
Normal file
125
FasdDesktopUi/Basics/Services/SupportCase/SupportCase.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using C4IT.FASD.Base;
|
||||
using FasdDesktopUi.Basics.Services.RelationService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.SupportCase
|
||||
{
|
||||
public class SupportCase : ISupportCase
|
||||
{
|
||||
private readonly Dictionary<enumFasdInformationClass, IList<cF4sdApiSearchResultRelation>> _caseRelations = new Dictionary<enumFasdInformationClass, IList<cF4sdApiSearchResultRelation>>();
|
||||
//private readonly Lookup<IdentitySet, cF4SDHealthCardRawData.cHealthCardTable> _supportCaseDataCache;
|
||||
|
||||
internal readonly Guid Id;
|
||||
private readonly IRelationService _relationService;
|
||||
public cSupportCaseDataProvider SupportCaseDataProviderArtifact { get; }
|
||||
|
||||
internal SupportCase(Guid id, IRelationService relationService, cSupportCaseDataProvider supportCaseDataProvider)
|
||||
{
|
||||
Id = id;
|
||||
_relationService = relationService;
|
||||
SupportCaseDataProviderArtifact = supportCaseDataProvider;
|
||||
|
||||
AddCaseRelations(_relationService?.GetLoadedRelations());
|
||||
}
|
||||
|
||||
~SupportCase()
|
||||
{
|
||||
if (_relationService != null)
|
||||
_relationService.RelationsFound -= HandleRelationsFound;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (_relationService != null)
|
||||
_relationService.RelationsFound += HandleRelationsFound;
|
||||
}
|
||||
|
||||
public ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> GetCaseRelations()
|
||||
{
|
||||
try
|
||||
{
|
||||
IEnumerable<(enumFasdInformationClass InformationClass, cF4sdApiSearchResultRelation Relation)> flatList
|
||||
= _caseRelations.SelectMany(i => i.Value.Select(v => (i.Key, v)));
|
||||
|
||||
return flatList.ToLookup(v => v.InformationClass, v => v.Relation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddCaseRelations(ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> relations)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (relations is null)
|
||||
return;
|
||||
|
||||
foreach (var relationType in relations)
|
||||
{
|
||||
if (_caseRelations.TryGetValue(relationType.Key, out var caseRelation))
|
||||
caseRelation = caseRelation.Union(relationType, new SearchResultRelationEqualityComparer()).ToList();
|
||||
else
|
||||
_caseRelations.Add(relationType.Key, relationType.ToList());
|
||||
|
||||
if (SupportCaseDataProviderArtifact?.CaseRelations?.TryGetValue(relationType.Key, out var caseRelations) ?? false)
|
||||
caseRelations = caseRelations.Union(relationType, new SearchResultRelationEqualityComparer()).ToList();
|
||||
else
|
||||
SupportCaseDataProviderArtifact?.CaseRelations?.Add(relationType.Key, relationType.ToList());
|
||||
}
|
||||
|
||||
CaseRelationsAdded?.Invoke(this, new RelationEventArgs() { Relations = relations });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSupportCaseDataCache()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<cF4SDHealthCardRawData.cHealthCardTable> GetSupportCaseHealthcardData(object identities, object valueAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
|
||||
{
|
||||
AddCaseRelations(e.StagedResultRelations.Relations);
|
||||
|
||||
if (e.StagedResultRelations.IsComplete)
|
||||
_relationService.RelationsFound -= HandleRelationsFound;
|
||||
}
|
||||
|
||||
private void AddCaseRelations(IEnumerable<cF4sdApiSearchResultRelation> relations)
|
||||
=> AddCaseRelations(relations?.ToLookup(r => cF4sdIdentityEntry.GetFromSearchResult(r.Type), r => r));
|
||||
|
||||
public event EventHandler<RelationEventArgs> CaseRelationsAdded;
|
||||
public event EventHandler<object> SupportCaseDataCacheHasChanged; // Lookup for IdentitySet and tables which has been updated
|
||||
|
||||
private class SearchResultRelationEqualityComparer : IEqualityComparer<cF4sdApiSearchResultRelation>
|
||||
{
|
||||
public bool Equals(cF4sdApiSearchResultRelation x, cF4sdApiSearchResultRelation y)
|
||||
{
|
||||
return x.isEqual(y);
|
||||
}
|
||||
|
||||
public int GetHashCode(cF4sdApiSearchResultRelation obj)
|
||||
{
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using C4IT.FASD.Base;
|
||||
using FasdDesktopUi.Basics.Services.RelationService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.SupportCase
|
||||
{
|
||||
public static class SupportCaseFactory
|
||||
{
|
||||
private static readonly Dictionary<Guid, ISupportCase> _supportCases = new Dictionary<Guid, ISupportCase>();
|
||||
|
||||
private static ISupportCase Create(cF4sdIdentityEntry primaryIdentity, IRelationService relationService, cSupportCaseDataProvider supportCaseDataProvider)
|
||||
{
|
||||
SupportCase supportCase = new SupportCase(primaryIdentity.Id, relationService.Clone(), supportCaseDataProvider);
|
||||
_supportCases.Add(primaryIdentity.Id, supportCase);
|
||||
supportCase.Initialize();
|
||||
return supportCase;
|
||||
}
|
||||
|
||||
public static ISupportCase Get(cF4sdIdentityEntry primaryIdentity, IRelationService relationService, cSupportCaseDataProvider supportCaseDataProvider)
|
||||
{
|
||||
if (primaryIdentity is null)
|
||||
throw new InvalidEnumArgumentException($"{nameof(primaryIdentity)} must not be null.");
|
||||
|
||||
if (primaryIdentity.Class != enumFasdInformationClass.User)
|
||||
throw new InvalidEnumArgumentException($"{nameof(primaryIdentity)} must be of class {nameof(enumFasdInformationClass.User)}.");
|
||||
|
||||
if (_supportCases.TryGetValue(primaryIdentity.Id, out var supportCase))
|
||||
{
|
||||
supportCase.Initialize();
|
||||
return supportCase;
|
||||
}
|
||||
|
||||
return Create(primaryIdentity, relationService, supportCaseDataProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.FASD.Cockpit.Communication;
|
||||
using FasdDesktopUi.Basics.Services.RelationService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static C4IT.Logging.cLogManager;
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services.SupportCaseSearchService
|
||||
{
|
||||
public class SupportCaseSearchService
|
||||
{
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public SupportCaseSearchService(IRelationService relationService)
|
||||
{
|
||||
_relationService = relationService;
|
||||
_relationService.RelationsFound += HandleRelationsFound;
|
||||
}
|
||||
|
||||
~SupportCaseSearchService()
|
||||
{
|
||||
_relationService.RelationsFound -= HandleRelationsFound;
|
||||
}
|
||||
|
||||
public static async Task<cFasdApiSearchResultCollection> GetSearchResultsAsync(string searchQuery, CancellationToken token = default)
|
||||
{
|
||||
Guid? searchId = null;
|
||||
try
|
||||
{
|
||||
searchId = await cFasdCockpitCommunicationBase.Instance.GetSearchResultsStart(searchQuery, token);
|
||||
|
||||
if (searchId is null)
|
||||
return new cFasdApiSearchResultCollection();
|
||||
|
||||
return await cFasdCockpitCommunicationBase.Instance.GetSearchResultsResult(searchId.Value, token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
if (searchId != null)
|
||||
await cFasdCockpitCommunicationBase.Instance.GetSearchResultsStop(searchId.Value, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
|
||||
return new cFasdApiSearchResultCollection();
|
||||
}
|
||||
|
||||
public async Task<cF4sdStagedSearchResultRelationTaskId> LoadRelationsAsync(IEnumerable<cFasdApiSearchResultEntry> relatedTo, CancellationToken token = default)
|
||||
=> await _relationService.LoadRelationsAsync(relatedTo, token);
|
||||
|
||||
private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
|
||||
{
|
||||
e.RelationService = _relationService;
|
||||
RelationsFound.Invoke(this, e);
|
||||
}
|
||||
|
||||
public event EventHandler<StagedSearchResultRelationsEventArgs> RelationsFound;
|
||||
}
|
||||
}
|
||||
600
FasdDesktopUi/Basics/Services/TicketOverviewUpdateService.cs
Normal file
600
FasdDesktopUi/Basics/Services/TicketOverviewUpdateService.cs
Normal file
@@ -0,0 +1,600 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.FASD.Cockpit.Communication;
|
||||
using FasdDesktopUi.Basics.Models;
|
||||
using FasdDesktopUi.Basics.Services.Models;
|
||||
#if isDemo
|
||||
using System.Net;
|
||||
using FasdCockpitCommunicationDemo;
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace FasdDesktopUi.Basics.Services
|
||||
{
|
||||
public sealed class TicketOverviewUpdateService
|
||||
{
|
||||
private static readonly TimeSpan RefreshInterval = TimeSpan.FromMinutes(5);
|
||||
private static readonly string[] OverviewKeys = new[]
|
||||
{
|
||||
"TicketsNew",
|
||||
"TicketsActive",
|
||||
"TicketsCritical",
|
||||
"TicketsNewInfo",
|
||||
"IncidentNew",
|
||||
"IncidentActive",
|
||||
"IncidentCritical",
|
||||
"IncidentNewInfo",
|
||||
"UnassignedTickets",
|
||||
"UnassignedTicketsCritical"
|
||||
};
|
||||
private const string DemoTicketDetailsKey = "Demo.HasTicketDetails";
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private readonly Dictionary<string, TileCounts> _currentCounts = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<(string Key, bool UseRoleScope), List<cF4sdApiSearchResultRelation>> _demoRelations = new Dictionary<(string, bool), List<cF4sdApiSearchResultRelation>>();
|
||||
private DispatcherTimer _timer;
|
||||
private bool _isFetching;
|
||||
private bool _fetchRetryPending;
|
||||
private bool _isDemo;
|
||||
private bool _initialized;
|
||||
private readonly Random _random = new Random();
|
||||
#if isDemo
|
||||
private readonly List<DemoTicketRecord> _persistedDemoTickets = new List<DemoTicketRecord>();
|
||||
private readonly List<DemoTicketTemplate> _demoTemplates = new List<DemoTicketTemplate>();
|
||||
private readonly HashSet<string> _usedSummaries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private const int SimulationHotkeyDelayMs = 400;
|
||||
private int _pendingSimulations;
|
||||
private DispatcherTimer _simulationFlushTimer;
|
||||
#endif
|
||||
|
||||
private TicketOverviewUpdateService()
|
||||
{
|
||||
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
|
||||
foreach (var key in OverviewKeys)
|
||||
{
|
||||
_currentCounts[key] = TileCounts.Empty;
|
||||
}
|
||||
#if isDemo
|
||||
_simulationFlushTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs), DispatcherPriority.Background, SimulationFlushTimer_Tick, _dispatcher)
|
||||
{
|
||||
IsEnabled = false
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
static TicketOverviewUpdateService()
|
||||
{
|
||||
#if isDemo
|
||||
Instance = new TicketOverviewUpdateService();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static TicketOverviewUpdateService Instance { get; } = null;
|
||||
|
||||
public event EventHandler<TicketOverviewCountsChangedEventArgs> OverviewCountsChanged;
|
||||
|
||||
public IReadOnlyDictionary<string, TileCounts> CurrentCounts => _currentCounts;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
_initialized = true;
|
||||
#if isDemo
|
||||
_isDemo = true;
|
||||
LoadPersistedDemoTickets();
|
||||
#else
|
||||
_isDemo = cFasdCockpitCommunicationBase.Instance?.IsDemo() == true;
|
||||
#endif
|
||||
|
||||
if (!_isDemo)
|
||||
{
|
||||
_timer = new DispatcherTimer(RefreshInterval, DispatcherPriority.Background, async (s, e) => await FetchAsync().ConfigureAwait(false), _dispatcher);
|
||||
_timer.Start();
|
||||
_ = FetchAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = FetchAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task FetchAsync()
|
||||
{
|
||||
if (_isFetching)
|
||||
return;
|
||||
|
||||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||||
if (communication == null)
|
||||
{
|
||||
ScheduleFetchRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
_isFetching = true;
|
||||
try
|
||||
{
|
||||
_isDemo = communication?.IsDemo() == true;
|
||||
if (_isDemo && _timer != null)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer = null;
|
||||
}
|
||||
var counts = await Task.Run(() => RetrieveCountsAsync()).ConfigureAwait(false);
|
||||
if (counts != null)
|
||||
{
|
||||
await _dispatcher.InvokeAsync(() => ProcessCounts(counts));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, TileCounts> RetrieveCountsAsync()
|
||||
{
|
||||
var communication = cFasdCockpitCommunicationBase.Instance;
|
||||
if (communication == null)
|
||||
return null;
|
||||
|
||||
var result = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var key in OverviewKeys)
|
||||
{
|
||||
var personalTask = communication.GetTicketOverviewRelations(key, useRoleScope: false, count: 0);
|
||||
var roleTask = communication.GetTicketOverviewRelations(key, useRoleScope: true, count: 0);
|
||||
Task.WaitAll(personalTask, roleTask);
|
||||
|
||||
int personalCount = personalTask.Result?.Count ?? 0;
|
||||
int roleCount = roleTask.Result?.Count ?? 0;
|
||||
|
||||
if (_isDemo)
|
||||
{
|
||||
personalCount += GetDemoRelationCount(key, useRoleScope: false);
|
||||
roleCount += GetDemoRelationCount(key, useRoleScope: true);
|
||||
}
|
||||
|
||||
result[key] = new TileCounts(personalCount, roleCount);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ProcessCounts(Dictionary<string, TileCounts> newCounts)
|
||||
{
|
||||
var changes = new List<TileCountChange>();
|
||||
bool hasPrevious = _currentCounts.Values.Any(tc => tc.Personal > 0 || tc.Role > 0);
|
||||
|
||||
foreach (var key in OverviewKeys)
|
||||
{
|
||||
var previous = _currentCounts[key];
|
||||
var current = newCounts.TryGetValue(key, out var value) ? value : TileCounts.Empty;
|
||||
|
||||
if (previous.Personal != current.Personal)
|
||||
{
|
||||
changes.Add(new TileCountChange(key, TileScope.Personal, previous.Personal, current.Personal));
|
||||
}
|
||||
|
||||
if (previous.Role != current.Role)
|
||||
{
|
||||
changes.Add(new TileCountChange(key, TileScope.Role, previous.Role, current.Role));
|
||||
}
|
||||
|
||||
_currentCounts[key] = current;
|
||||
}
|
||||
|
||||
if (!hasPrevious)
|
||||
return;
|
||||
|
||||
if (changes.Count == 0)
|
||||
return;
|
||||
|
||||
var args = new TicketOverviewCountsChangedEventArgs(changes, new Dictionary<string, TileCounts>(_currentCounts));
|
||||
OverviewCountsChanged?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public void SimulateDemoTicket()
|
||||
{
|
||||
_isDemo = cFasdCockpitCommunicationBase.Instance?.IsDemo() == true;
|
||||
if (!_isDemo)
|
||||
return;
|
||||
|
||||
#if isDemo
|
||||
if (_demoTemplates.Count == 0)
|
||||
{
|
||||
LoadDemoTemplates();
|
||||
if (_demoTemplates.Count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingSimulations++;
|
||||
|
||||
if (_simulationFlushTimer != null)
|
||||
{
|
||||
_simulationFlushTimer.Stop();
|
||||
_simulationFlushTimer.Interval = TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs);
|
||||
_simulationFlushTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessDemoSimulations(_pendingSimulations);
|
||||
_pendingSimulations = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public IEnumerable<cF4sdApiSearchResultRelation> GetDemoRelations(string key, bool useRoleScope)
|
||||
{
|
||||
if (!_isDemo)
|
||||
return Enumerable.Empty<cF4sdApiSearchResultRelation>();
|
||||
|
||||
lock (_demoRelations)
|
||||
{
|
||||
if (_demoRelations.TryGetValue((key, useRoleScope), out var list))
|
||||
return list.ToList();
|
||||
}
|
||||
|
||||
return Enumerable.Empty<cF4sdApiSearchResultRelation>();
|
||||
}
|
||||
|
||||
#if isDemo
|
||||
private void LoadPersistedDemoTickets()
|
||||
{
|
||||
var data = TicketOverviewDataStore.LoadData();
|
||||
|
||||
_demoTemplates.Clear();
|
||||
if (data.Templates != null)
|
||||
_demoTemplates.AddRange(data.Templates);
|
||||
|
||||
_persistedDemoTickets.Clear();
|
||||
_usedSummaries.Clear();
|
||||
if (data.Tickets == null)
|
||||
return;
|
||||
|
||||
foreach (var record in data.Tickets)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(record.Summary))
|
||||
_usedSummaries.Add(record.Summary);
|
||||
|
||||
_persistedDemoTickets.Add(record);
|
||||
AddRelationForRecord(record);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDemoTemplates()
|
||||
{
|
||||
var templates = TicketOverviewDataStore.LoadTemplates();
|
||||
if (templates == null || templates.Count == 0)
|
||||
return;
|
||||
|
||||
_demoTemplates.Clear();
|
||||
_demoTemplates.AddRange(templates);
|
||||
}
|
||||
|
||||
private void SimulationFlushTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
_simulationFlushTimer.Stop();
|
||||
var count = _pendingSimulations;
|
||||
_pendingSimulations = 0;
|
||||
ProcessDemoSimulations(count);
|
||||
}
|
||||
|
||||
private void ProcessDemoSimulations(int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
return;
|
||||
|
||||
if (_demoTemplates.Count == 0)
|
||||
{
|
||||
LoadDemoTemplates();
|
||||
if (_demoTemplates.Count == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
var appliedChanges = new List<TileCountChange>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var template = _demoTemplates[_random.Next(_demoTemplates.Count)];
|
||||
var record = CreateDemoTicketRecord(template);
|
||||
if (record == null)
|
||||
continue;
|
||||
|
||||
if (!TicketOverviewDataStore.AppendTicket(record))
|
||||
continue;
|
||||
|
||||
var change = RegisterDemoTicket(record);
|
||||
if (change.HasValue)
|
||||
appliedChanges.Add(change.Value);
|
||||
}
|
||||
|
||||
if (appliedChanges.Count == 0)
|
||||
return;
|
||||
|
||||
var args = new TicketOverviewCountsChangedEventArgs(appliedChanges, new Dictionary<string, TileCounts>(_currentCounts));
|
||||
OverviewCountsChanged?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void AddRelationForRecord(DemoTicketRecord record)
|
||||
{
|
||||
if (record == null)
|
||||
return;
|
||||
|
||||
var relation = CreateRelationFromRecord(record);
|
||||
var scopeKey = (record.TileKey, record.UseRoleScope);
|
||||
|
||||
lock (_demoRelations)
|
||||
{
|
||||
if (!_demoRelations.TryGetValue(scopeKey, out var list))
|
||||
{
|
||||
list = new List<cF4sdApiSearchResultRelation>();
|
||||
_demoRelations[scopeKey] = list;
|
||||
}
|
||||
|
||||
if (list.Any(existing => existing.id == relation.id))
|
||||
return;
|
||||
|
||||
list.Add(relation);
|
||||
}
|
||||
}
|
||||
|
||||
private cF4sdApiSearchResultRelation CreateRelationFromRecord(DemoTicketRecord record)
|
||||
{
|
||||
var relation = new cF4sdApiSearchResultRelation
|
||||
{
|
||||
Type = enumF4sdSearchResultClass.Ticket,
|
||||
DisplayName = record.DisplayName,
|
||||
Name = record.DisplayName,
|
||||
id = record.TicketId,
|
||||
Status = enumF4sdSearchResultStatus.Active,
|
||||
Infos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["Summary"] = record.Summary ?? string.Empty,
|
||||
["StatusId"] = record.StatusId ?? string.Empty,
|
||||
["UserDisplayName"] = record.UserDisplayName ?? string.Empty,
|
||||
["UserAccount"] = record.UserAccount ?? string.Empty,
|
||||
["UserDomain"] = record.UserDomain ?? string.Empty,
|
||||
[DemoTicketDetailsKey] = bool.TrueString
|
||||
},
|
||||
Identities = new cF4sdIdentityList
|
||||
{
|
||||
new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = record.TicketId },
|
||||
new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = record.UserId }
|
||||
}
|
||||
};
|
||||
|
||||
return relation;
|
||||
}
|
||||
|
||||
private DemoTicketDetail CloneDetail(DemoTicketDetail source)
|
||||
{
|
||||
if (source == null)
|
||||
return new DemoTicketDetail();
|
||||
|
||||
return new DemoTicketDetail
|
||||
{
|
||||
AffectedUser = source.AffectedUser,
|
||||
Asset = source.Asset,
|
||||
Category = source.Category,
|
||||
Classification = source.Classification,
|
||||
Description = source.Description,
|
||||
DescriptionHtml = source.DescriptionHtml,
|
||||
Priority = source.Priority,
|
||||
Solution = source.Solution,
|
||||
SolutionHtml = source.SolutionHtml,
|
||||
Journal = source.Journal?.Select(entry => new DemoTicketJournalEntry
|
||||
{
|
||||
Header = entry?.Header,
|
||||
Description = entry?.Description,
|
||||
DescriptionHtml = entry?.DescriptionHtml,
|
||||
IsVisibleForUser = entry?.IsVisibleForUser ?? true,
|
||||
CreationDate = entry?.CreationDate ?? default
|
||||
}).ToList() ?? new List<DemoTicketJournalEntry>()
|
||||
};
|
||||
}
|
||||
|
||||
private TileCountChange? RegisterDemoTicket(DemoTicketRecord record)
|
||||
{
|
||||
if (record == null)
|
||||
return null;
|
||||
|
||||
_persistedDemoTickets.Add(record);
|
||||
AddRelationForRecord(record);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(record.Summary))
|
||||
_usedSummaries.Add(record.Summary);
|
||||
|
||||
if (cFasdCockpitCommunicationBase.Instance is cFasdCockpitCommunicationDemo demoCommunication)
|
||||
{
|
||||
demoCommunication.RegisterGeneratedTicket(record);
|
||||
}
|
||||
|
||||
if (!_currentCounts.TryGetValue(record.TileKey, out var previousCounts))
|
||||
previousCounts = TileCounts.Empty;
|
||||
|
||||
TileCounts updatedCounts;
|
||||
int oldValue;
|
||||
int newValue;
|
||||
TileScope scope;
|
||||
|
||||
if (record.UseRoleScope)
|
||||
{
|
||||
updatedCounts = new TileCounts(previousCounts.Personal, previousCounts.Role + 1);
|
||||
oldValue = previousCounts.Role;
|
||||
newValue = updatedCounts.Role;
|
||||
scope = TileScope.Role;
|
||||
}
|
||||
else
|
||||
{
|
||||
updatedCounts = new TileCounts(previousCounts.Personal + 1, previousCounts.Role);
|
||||
oldValue = previousCounts.Personal;
|
||||
newValue = updatedCounts.Personal;
|
||||
scope = TileScope.Personal;
|
||||
}
|
||||
|
||||
_currentCounts[record.TileKey] = updatedCounts;
|
||||
|
||||
return new TileCountChange(record.TileKey, scope, oldValue, newValue);
|
||||
}
|
||||
|
||||
private string EnsureUniqueSummary(string preferredSummary)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(preferredSummary))
|
||||
preferredSummary = "Demo Ticket";
|
||||
|
||||
if (!_usedSummaries.Contains(preferredSummary))
|
||||
{
|
||||
_usedSummaries.Add(preferredSummary);
|
||||
return preferredSummary;
|
||||
}
|
||||
|
||||
var nextFreeSummary = _demoTemplates
|
||||
.Select(t => t?.Summary)
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.FirstOrDefault(s => !_usedSummaries.Contains(s));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(nextFreeSummary))
|
||||
{
|
||||
_usedSummaries.Add(nextFreeSummary);
|
||||
return nextFreeSummary;
|
||||
}
|
||||
|
||||
var baseSummary = preferredSummary;
|
||||
var suffix = 2;
|
||||
var candidate = baseSummary;
|
||||
while (_usedSummaries.Contains(candidate))
|
||||
{
|
||||
candidate = $"{baseSummary} #{suffix}";
|
||||
suffix++;
|
||||
}
|
||||
|
||||
_usedSummaries.Add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
||||
private DemoTicketRecord CreateDemoTicketRecord(DemoTicketTemplate template)
|
||||
{
|
||||
if (template == null)
|
||||
return null;
|
||||
|
||||
var relationId = Guid.NewGuid();
|
||||
var createdAt = DateTime.UtcNow;
|
||||
var prefix = string.IsNullOrWhiteSpace(template.DisplayNamePrefix) ? "TCK" : template.DisplayNamePrefix.Trim();
|
||||
prefix = prefix.ToUpperInvariant();
|
||||
var displayName = TicketOverviewDataStore.GetNextDisplayName(prefix);
|
||||
var summary = EnsureUniqueSummary(template.Summary ?? string.Empty);
|
||||
var detail = CloneDetail(template.Detail);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.AffectedUser))
|
||||
detail.AffectedUser = template.UserDisplayName ?? "Ticket, Timo";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.Description) && !string.IsNullOrWhiteSpace(detail.DescriptionHtml))
|
||||
{
|
||||
detail.Description = Regex.Replace(detail.DescriptionHtml, "<.*?>", string.Empty);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.Description))
|
||||
detail.Description = summary;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.DescriptionHtml))
|
||||
{
|
||||
detail.DescriptionHtml = $"<p>{WebUtility.HtmlEncode(detail.Description)}</p>";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.Solution) && !string.IsNullOrWhiteSpace(detail.SolutionHtml))
|
||||
{
|
||||
detail.Solution = Regex.Replace(detail.SolutionHtml, "<.*?>", string.Empty);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.Solution))
|
||||
detail.Solution = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(detail.SolutionHtml) && !string.IsNullOrWhiteSpace(detail.Solution))
|
||||
{
|
||||
detail.SolutionHtml = $"<p>{WebUtility.HtmlEncode(detail.Solution)}</p>";
|
||||
}
|
||||
|
||||
if (detail.Journal == null || detail.Journal.Count == 0)
|
||||
{
|
||||
detail.Journal = new List<DemoTicketJournalEntry>
|
||||
{
|
||||
new DemoTicketJournalEntry
|
||||
{
|
||||
Header = "Ticket erstellt",
|
||||
Description = detail.Description ?? "Automatisch generiertes Demoticket.",
|
||||
DescriptionHtml = detail.DescriptionHtml ?? "<p>Automatisch generiertes Demoticket.</p>",
|
||||
IsVisibleForUser = true,
|
||||
CreationDate = createdAt
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var entry in detail.Journal)
|
||||
{
|
||||
if (entry.CreationDate == default)
|
||||
entry.CreationDate = createdAt;
|
||||
}
|
||||
|
||||
return new DemoTicketRecord
|
||||
{
|
||||
TicketId = relationId,
|
||||
TileKey = string.IsNullOrWhiteSpace(template.TileKey) ? "TicketsNew" : template.TileKey,
|
||||
UseRoleScope = template.UseRoleScope,
|
||||
DisplayName = displayName,
|
||||
Summary = summary,
|
||||
StatusId = string.IsNullOrWhiteSpace(template.StatusId) ? "New" : template.StatusId,
|
||||
UserDisplayName = template.UserDisplayName ?? detail.AffectedUser ?? "Ticket, Timo",
|
||||
UserAccount = template.UserAccount ?? "TT007",
|
||||
UserDomain = template.UserDomain ?? "CONTOSO",
|
||||
UserId = template.UserId ?? Guid.Parse("42c760d6-90e8-469f-b2fe-ac7d4cc6cb0a"),
|
||||
CreatedAt = createdAt,
|
||||
Detail = detail
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public Dictionary<string, int> GetCountsForScope(bool useRoleScope)
|
||||
{
|
||||
return _currentCounts.ToDictionary(kvp => kvp.Key, kvp => useRoleScope ? kvp.Value.Role : kvp.Value.Personal, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private int GetDemoRelationCount(string key, bool useRoleScope)
|
||||
{
|
||||
lock (_demoRelations)
|
||||
{
|
||||
if (_demoRelations.TryGetValue((key, useRoleScope), out var list))
|
||||
return list.Count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ScheduleFetchRetry()
|
||||
{
|
||||
if (_fetchRetryPending)
|
||||
return;
|
||||
|
||||
_fetchRetryPending = true;
|
||||
_ = _dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(250).ConfigureAwait(false);
|
||||
await FetchAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_fetchRetryPending = false;
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user