aktueller stand

This commit is contained in:
Meik
2026-02-12 16:15:06 +01:00
parent 493e671b9f
commit abbce22aa9
17 changed files with 2220 additions and 1321 deletions

View File

@@ -31,23 +31,20 @@ namespace C4IT.FASD.Cockpit.Communication
#region Ticketübersicht
private readonly Dictionary<string, Dictionary<string, List<TicketOverviewRelationDefinition>>> TicketOverviewRelations =
new Dictionary<string, Dictionary<string, List<TicketOverviewRelationDefinition>>>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<Guid> _generatedTicketIds = new HashSet<Guid>();
private readonly Dictionary<string, Dictionary<string, List<DemoTicketRecord>>> TicketOverviewRelations =
new Dictionary<string, Dictionary<string, List<DemoTicketRecord>>>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _loadedOverviewPlacements = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly object _demoTicketSync = new object();
#endregion
public cFasdCockpitCommunicationDemo()
{
if (LoadMockupData(out var loadedData))
MockupData = loadedData;
public cFasdCockpitCommunicationDemo()
{
if (LoadMockupData(out var loadedData))
MockupData = loadedData;
MockupPickup = LoadMockupPickup();
BuildCategoryLookup();
LoadTicketOverviewRelations();
LoadGeneratedTickets();
ApplyMissingDemoTicketActivityTypes();
EnsureOverviewTicketJournalEntries();
}
public override bool IsDemo() => true;
@@ -67,12 +64,10 @@ namespace C4IT.FASD.Cockpit.Communication
var sampleDataFiles = Directory.GetFiles(path);
foreach (var file in sampleDataFiles)
{
if (string.Equals(Path.GetFileName(file), "TicketOverviewRelations.json", StringComparison.OrdinalIgnoreCase))
continue;
try
{
foreach (var file in sampleDataFiles)
{
try
{
string jsonText;
using (StreamReader streamReader = new StreamReader(file))
{
@@ -149,60 +144,18 @@ namespace C4IT.FASD.Cockpit.Communication
#region Ticketübersicht
private void LoadTicketOverviewRelations()
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
TicketOverviewRelations.Clear();
string executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string filePath = Path.Combine(executingDirectory, "MockupTicketOverview", "TicketOverviewRelations.json");
if (!File.Exists(filePath))
{
LogEntry($"Ticket overview demo data missing at {filePath}", LogLevels.Warning);
return;
}
string jsonText;
using (StreamReader streamReader = new StreamReader(filePath))
{
jsonText = streamReader.ReadToEnd();
}
var rawData = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, List<TicketOverviewRelationDefinition>>>>(jsonText);
if (rawData == null)
return;
foreach (var tileEntry in rawData)
{
var scopeDictionary = new Dictionary<string, List<TicketOverviewRelationDefinition>>(StringComparer.OrdinalIgnoreCase);
if (tileEntry.Value != null)
{
foreach (var scopeEntry in tileEntry.Value)
{
scopeDictionary[scopeEntry.Key ?? string.Empty] = scopeEntry.Value ?? new List<TicketOverviewRelationDefinition>();
}
}
TicketOverviewRelations[tileEntry.Key] = scopeDictionary;
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
LogMethodEnd(CM);
}
}
private void LoadGeneratedTickets()
{
try
{
TicketOverviewRelations.Clear();
_loadedOverviewPlacements.Clear();
var records = TicketOverviewDataStore.LoadTickets();
foreach (var record in records)
{
AppendDemoTicket(record);
}
foreach (var record in records)
{
AppendDemoTicket(record);
}
}
catch (Exception E)
{
@@ -222,226 +175,47 @@ namespace C4IT.FASD.Cockpit.Communication
}
}
private void AppendDemoTicket(DemoTicketRecord record)
{
if (record == null)
return;
lock (_demoTicketSync)
{
if (!_generatedTicketIds.Add(record.TicketId))
return;
var scopeKey = record.UseRoleScope ? "Role" : "Personal";
if (!TicketOverviewRelations.TryGetValue(record.TileKey, out var scopeDictionary))
{
scopeDictionary = new Dictionary<string, List<TicketOverviewRelationDefinition>>(StringComparer.OrdinalIgnoreCase);
TicketOverviewRelations[record.TileKey] = scopeDictionary;
}
if (!scopeDictionary.TryGetValue(scopeKey, out var definitions))
{
definitions = new List<TicketOverviewRelationDefinition>();
scopeDictionary[scopeKey] = definitions;
}
if (!definitions.Any(d => d.TicketId == record.TicketId))
private void AppendDemoTicket(DemoTicketRecord record)
{
if (record == null)
return;
lock (_demoTicketSync)
{
if (record.TicketId == Guid.Empty || string.IsNullOrWhiteSpace(record.TileKey))
return;
var tileKey = record.TileKey.Trim();
var scopeKey = record.UseRoleScope ? "Role" : "Personal";
var placementKey = $"{record.TicketId:N}|{tileKey}|{scopeKey}";
if (!_loadedOverviewPlacements.Add(placementKey))
return;
if (!TicketOverviewRelations.TryGetValue(tileKey, out var scopeDictionary))
{
definitions.Add(new TicketOverviewRelationDefinition
{
TicketId = record.TicketId,
UserId = record.UserId,
DisplayName = record.DisplayName,
ActivityType = record.ActivityType,
Summary = record.Summary,
StatusId = record.StatusId,
UserDisplayName = record.UserDisplayName,
UserAccount = record.UserAccount,
UserDomain = record.UserDomain
});
}
var targetSample = MockupData.FirstOrDefault(data => data.SampleDataId == record.UserId);
if (targetSample == null)
return;
scopeDictionary = new Dictionary<string, List<DemoTicketRecord>>(StringComparer.OrdinalIgnoreCase);
TicketOverviewRelations[tileKey] = scopeDictionary;
}
if (!scopeDictionary.TryGetValue(scopeKey, out var definitions))
{
definitions = new List<DemoTicketRecord>();
scopeDictionary[scopeKey] = definitions;
}
definitions.Add(record);
var targetSample = MockupData.FirstOrDefault(data => data.SampleDataId == record.UserId);
if (targetSample == null)
return;
if (targetSample.Tickets.Any(ticket => ticket.Id == record.TicketId))
return;
var generatedTicket = ConvertToTicket(record);
var generatedTicket = ConvertToTicket(record);
targetSample.Tickets.Add(generatedTicket);
EnsureTicketHasMinimumDemoJournalEntries(generatedTicket);
}
}
private void ApplyMissingDemoTicketActivityTypes()
{
try
{
var activityTypesByTicketId = new Dictionary<Guid, string>();
foreach (var tileEntry in TicketOverviewRelations.Values)
{
if (tileEntry == null)
continue;
foreach (var scopeEntry in tileEntry.Values)
{
if (scopeEntry == null)
continue;
foreach (var definition in scopeEntry)
{
if (definition == null || definition.TicketId == Guid.Empty)
continue;
var activityType = NormalizeActivityType(definition.ActivityType);
if (string.IsNullOrWhiteSpace(activityType))
continue;
activityTypesByTicketId[definition.TicketId] = activityType;
}
}
}
foreach (var sampleData in MockupData)
{
if (sampleData?.Tickets == null)
continue;
foreach (var ticket in sampleData.Tickets)
{
if (ticket == null)
continue;
ticket.ActivityType = NormalizeActivityType(ticket.ActivityType);
if (!string.IsNullOrWhiteSpace(ticket.ActivityType))
continue;
if (ticket.Id != Guid.Empty && activityTypesByTicketId.TryGetValue(ticket.Id, out var mappedActivityType))
{
ticket.ActivityType = mappedActivityType;
continue;
}
ticket.ActivityType = NormalizeActivityType(TryGetActivityTypeFromTicketLinks(ticket));
}
}
}
catch (Exception E)
{
LogException(E);
}
}
private static string NormalizeActivityType(string activityType)
{
return string.IsNullOrWhiteSpace(activityType) ? null : activityType.Trim();
}
private void EnsureOverviewTicketJournalEntries()
{
try
{
var overviewTicketIds = TicketOverviewRelations.Values
.Where(scopeMap => scopeMap != null)
.SelectMany(scopeMap => scopeMap.Values)
.Where(definitions => definitions != null)
.SelectMany(definitions => definitions)
.Where(definition => definition != null && definition.TicketId != Guid.Empty)
.Select(definition => definition.TicketId)
.Distinct()
.ToList();
foreach (var ticketId in overviewTicketIds)
{
var ticket = MockupData
.FirstOrDefault(data => data != null && data.Tickets != null && data.Tickets.Any(t => t.Id == ticketId))
?.Tickets
.FirstOrDefault(t => t.Id == ticketId);
if (ticket == null)
continue;
EnsureTicketHasMinimumDemoJournalEntries(ticket);
}
}
catch (Exception E)
{
LogException(E);
}
}
private void EnsureTicketHasMinimumDemoJournalEntries(cF4SDTicket ticket)
{
if (ticket == null)
return;
if (ticket.JournalItems == null)
ticket.JournalItems = new List<cF4SDTicket.cTicketJournalItem>();
if (ticket.JournalItems.Count >= 2)
return;
bool isIncident = (ticket.Name ?? string.Empty).StartsWith("INC", StringComparison.OrdinalIgnoreCase);
var baseTime = ticket.CreationDate == default ? DateTime.Now.AddHours(-2) : ticket.CreationDate;
if (ticket.JournalItems.Count == 0)
{
ticket.JournalItems.Add(CreateDemoJournalItem(
baseTime,
isIncident ? "Incident erfasst" : "Ticket erfasst",
"Der Vorgang wurde in der Ticketuebersicht erfasst und priorisiert."));
}
if (ticket.JournalItems.Count < 2)
{
string header;
string text;
switch (ticket.Status)
{
case enumTicketStatus.InProgress:
header = "Bearbeitung aufgenommen";
text = "Der Vorgang wurde einem Bearbeiter zugeordnet und die Analyse gestartet.";
break;
case enumTicketStatus.OnHold:
header = "Warte auf Rueckmeldung";
text = "Fuer die weitere Bearbeitung werden zusaetzliche Informationen vom Anwender erwartet.";
break;
case enumTicketStatus.New:
header = "Sichtung durch Service Desk";
text = "Der Vorgang wurde gesichtet und zur Bearbeitung vorbereitet.";
break;
case enumTicketStatus.Closed:
header = "Abschluss dokumentiert";
text = "Loesung und Abschluss wurden fuer den Vorgang dokumentiert.";
break;
default:
header = "Status aktualisiert";
text = "Der aktuelle Bearbeitungsstand wurde im Vorgang aktualisiert.";
break;
}
ticket.JournalItems.Add(CreateDemoJournalItem(baseTime.AddMinutes(15), header, text));
}
}
private static cF4SDTicket.cTicketJournalItem CreateDemoJournalItem(DateTime timestamp, string header, string text)
{
var creationTime = timestamp == default ? DateTime.Now : timestamp;
return new cF4SDTicket.cTicketJournalItem
{
Header = header ?? string.Empty,
Description = text ?? string.Empty,
DescriptionHtml = $"<p>{text ?? string.Empty}</p>",
IsVisibleForUser = true,
CreationDate = creationTime,
CreationDaysSinceNow = Math.Max(0, (int)(DateTime.Now - creationTime).TotalDays)
};
}
private static cF4SDTicket ConvertToTicket(DemoTicketRecord record)
{
@@ -461,7 +235,7 @@ namespace C4IT.FASD.Cockpit.Communication
AffectedUser = detail.AffectedUser ?? record.UserDisplayName,
Asset = detail.Asset,
Category = detail.Category,
ActivityType = NormalizeActivityType(record.ActivityType),
ActivityType = record.ActivityType,
Description = detail.Description,
DescriptionHtml = detail.DescriptionHtml,
Solution = detail.Solution,
@@ -600,137 +374,19 @@ namespace C4IT.FASD.Cockpit.Communication
}
}
private cF4SDTicket FindTicketForOverviewRelation(TicketOverviewRelationDefinition definition)
private cF4SDTicket FindTicketForOverviewRelation(DemoTicketRecord definition)
{
if (definition == null || definition.TicketId == Guid.Empty)
return null;
cF4SDTicket selectedTicket = null;
if (definition.UserId != Guid.Empty)
{
var selectedData = MockupData.FirstOrDefault(data => data.SampleDataId == definition.UserId);
if (selectedData != null && selectedData.Tickets != null)
selectedTicket = selectedData.Tickets.FirstOrDefault(ticket => ticket.Id == definition.TicketId);
}
if (selectedTicket != null)
return selectedTicket;
var fallbackData = MockupData.FirstOrDefault(data =>
data != null &&
data.Tickets != null &&
data.Tickets.Any(ticket => ticket.Id == definition.TicketId));
if (fallbackData == null || fallbackData.Tickets == null)
if (definition.UserId == Guid.Empty)
return null;
return fallbackData.Tickets.FirstOrDefault(ticket => ticket.Id == definition.TicketId);
}
private static string ResolveTicketActivityType(cF4SDTicket detailTicket = null)
{
var configuredType = NormalizeActivityType(detailTicket?.ActivityType);
if (!string.IsNullOrWhiteSpace(configuredType))
return configuredType;
var linkType = TryGetActivityTypeFromTicketLinks(detailTicket);
if (!string.IsNullOrWhiteSpace(linkType))
return linkType;
return "SPSActivityTypeTicket";
}
private static string TryGetActivityTypeFromTicketLinks(cF4SDTicket ticket)
{
if (ticket?.DirectLinks == null || ticket.DirectLinks.Count == 0)
var selectedData = MockupData.FirstOrDefault(data => data.SampleDataId == definition.UserId);
if (selectedData?.Tickets == null)
return null;
foreach (var link in ticket.DirectLinks.Values)
{
var type = TryGetActivityTypeFromUrl(link);
if (!string.IsNullOrWhiteSpace(type))
return type;
}
return null;
}
private static string TryGetActivityTypeFromUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
return null;
var markerList = new[] { "preview-object/", "edit-object/", "create-object/" };
foreach (var marker in markerList)
{
var markerIndex = url.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
if (markerIndex < 0)
continue;
var start = markerIndex + marker.Length;
var end = url.IndexOf('/', start);
if (end < 0)
end = url.IndexOf('?', start);
if (end < 0)
end = url.Length;
if (end <= start)
continue;
var activityType = url.Substring(start, end - start).Trim();
if (!string.IsNullOrWhiteSpace(activityType))
return activityType;
}
// Fallback for view-options based deeplinks.
var decodedUrl = Uri.UnescapeDataString(url);
const string queryTypeToken = "\"type\":\"";
var typeStart = decodedUrl.IndexOf(queryTypeToken, StringComparison.OrdinalIgnoreCase);
if (typeStart < 0)
return null;
typeStart += queryTypeToken.Length;
var typeEnd = decodedUrl.IndexOf('"', typeStart);
if (typeEnd <= typeStart)
return null;
var parsedType = decodedUrl.Substring(typeStart, typeEnd - typeStart).Trim();
return string.IsNullOrWhiteSpace(parsedType) ? null : parsedType;
}
private string ResolveDemoM42Server()
{
foreach (var sampleData in MockupData)
{
if (sampleData?.Tickets == null)
continue;
foreach (var ticket in sampleData.Tickets)
{
if (ticket?.DirectLinks == null)
continue;
foreach (var link in ticket.DirectLinks.Values)
{
if (TryExtractServerBase(link, out var serverBase))
return serverBase;
}
}
}
return "https://srvwsm001.imagoverum.com";
}
private static bool TryExtractServerBase(string url, out string serverBase)
{
serverBase = null;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
return false;
serverBase = uri.GetLeftPart(UriPartial.Authority);
return !string.IsNullOrWhiteSpace(serverBase);
return selectedData.Tickets.FirstOrDefault(ticket => ticket.Id == definition.TicketId);
}
private static Task SimulateTicketOverviewLatencyAsync(int count)
@@ -1012,91 +668,9 @@ namespace C4IT.FASD.Cockpit.Communication
var output = new cFasdApiSearchResultCollection();
try
{
bool IsNameMatch(string sourceName, string requestName)
{
if (string.IsNullOrWhiteSpace(sourceName) || string.IsNullOrWhiteSpace(requestName))
return false;
if (sourceName.Equals(requestName, StringComparison.InvariantCultureIgnoreCase))
return true;
string Normalize(string value)
{
return string.Join(" ", (value ?? string.Empty)
.ToLowerInvariant()
.Replace(",", " ")
.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
}
int LevenshteinDistance(string left, string right)
{
if (string.IsNullOrEmpty(left))
return right?.Length ?? 0;
if (string.IsNullOrEmpty(right))
return left.Length;
int[] previous = new int[right.Length + 1];
int[] current = new int[right.Length + 1];
for (int j = 0; j <= right.Length; j++)
previous[j] = j;
for (int i = 1; i <= left.Length; i++)
{
current[0] = i;
for (int j = 1; j <= right.Length; j++)
{
int cost = left[i - 1] == right[j - 1] ? 0 : 1;
current[j] = Math.Min(
Math.Min(current[j - 1] + 1, previous[j] + 1),
previous[j - 1] + cost);
}
var temp = previous;
previous = current;
current = temp;
}
return previous[right.Length];
}
bool IsTokenMatch(string leftToken, string rightToken)
{
if (leftToken == rightToken)
return true;
int sharedLength = Math.Min(Math.Min(leftToken.Length, rightToken.Length), 5);
if (sharedLength >= 4 && leftToken.Substring(0, sharedLength) == rightToken.Substring(0, sharedLength))
return true;
return LevenshteinDistance(leftToken, rightToken) <= 2;
}
var normalizedSource = Normalize(sourceName);
var normalizedRequest = Normalize(requestName);
if (normalizedSource == normalizedRequest)
return true;
var sourceTokens = normalizedSource.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
var requestTokens = normalizedRequest.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (sourceTokens.Count == 0 || requestTokens.Count == 0 || sourceTokens.Count != requestTokens.Count)
return false;
var remaining = new List<string>(sourceTokens);
foreach (var token in requestTokens)
{
int index = remaining.FindIndex(candidate => IsTokenMatch(candidate, token));
if (index < 0)
return false;
remaining.RemoveAt(index);
}
return remaining.Count == 0;
}
foreach (var data in MockupData)
{
if (!IsNameMatch(data.SampleDataName, Name))
if (!string.Equals(data.SampleDataName, Name, StringComparison.InvariantCultureIgnoreCase))
continue;
//todo: add a field in demo data
@@ -1306,7 +880,7 @@ namespace C4IT.FASD.Cockpit.Communication
foreach (var demoTicket in demoTickets)
{
output.Add(new cF4sdApiSearchResultRelation() { id = demoTicket.Id, Name = demoTicket.Name, DisplayName = demoTicket.Name, Infos = new Dictionary<string, string>() { ["Summary"] = demoTicket.Summary, ["Status"] = demoTicket.Status.ToString(), ["StatusId"] = ((int)demoTicket.Status).ToString(), ["Asset"] = demoTicket.Asset, ["ActivityType"] = ResolveTicketActivityType(demoTicket) }, Type = enumF4sdSearchResultClass.Ticket, Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = Guid.Parse(constGuidTimoTicket) }, new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Ticket, Id = demoTicket.Id } } });
output.Add(new cF4sdApiSearchResultRelation() { id = demoTicket.Id, Name = demoTicket.Name, DisplayName = demoTicket.Name, Infos = new Dictionary<string, string>() { ["Summary"] = demoTicket.Summary, ["Status"] = demoTicket.Status.ToString(), ["StatusId"] = ((int)demoTicket.Status).ToString(), ["Asset"] = demoTicket.Asset, ["ActivityType"] = demoTicket.ActivityType }, Type = enumF4sdSearchResultClass.Ticket, Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = Guid.Parse(constGuidTimoTicket) }, new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Ticket, Id = demoTicket.Id } } });
}
break;
@@ -1366,7 +940,7 @@ namespace C4IT.FASD.Cockpit.Communication
["Status"] = demoTicket.Status.ToString(),
["StatusId"] = ((int)demoTicket.Status).ToString(),
["Asset"] = demoTicket.Asset ?? string.Empty,
["ActivityType"] = ResolveTicketActivityType(demoTicket)
["ActivityType"] = demoTicket.ActivityType
},
Identities = new cF4sdIdentityList()
{
@@ -1387,20 +961,9 @@ namespace C4IT.FASD.Cockpit.Communication
try
{
await Task.Delay(250);
var selectedData = MockupData.FirstOrDefault(data => requestData.Identities.Any(identity => identity.Id == data.SampleDataId));
await Task.Delay(250);
if (selectedData is null)
{
var ticketIdentity = requestData.Identities?
.FirstOrDefault(identity => identity.Class is enumFasdInformationClass.Ticket);
if (ticketIdentity != null)
{
selectedData = MockupData.FirstOrDefault(data =>
data?.Tickets != null && data.Tickets.Any(ticket => ticket?.Id == ticketIdentity.Id));
}
}
var selectedData = MockupData.FirstOrDefault(data => requestData.Identities.Any(identity => identity.Id == data.SampleDataId));
if (selectedData is null)
return output;
@@ -1411,20 +974,6 @@ namespace C4IT.FASD.Cockpit.Communication
{
var selectedTicket = selectedData.Tickets.FirstOrDefault(ticket => ticket.Id == ticketRequest.Id);
// Some demo users share the same SampleDataId; resolve by TicketId if the first user match has no ticket details.
if (selectedTicket == null)
{
var fallbackData = MockupData.FirstOrDefault(data =>
data?.Tickets != null && data.Tickets.Any(ticket => ticket?.Id == ticketRequest.Id));
if (fallbackData != null)
{
selectedData = fallbackData;
output = selectedData.GetHealthCardData();
selectedTicket = selectedData.Tickets.FirstOrDefault(ticket => ticket.Id == ticketRequest.Id);
}
}
if (selectedTicket != null)
{
string ticketStatusString = string.Empty;
@@ -1953,7 +1502,7 @@ namespace C4IT.FASD.Cockpit.Communication
output.Category = category.ToString();
if (writeParams.Values.TryGetValue("ActivityType", out var activityType))
output.ActivityType = NormalizeActivityType(activityType?.ToString());
output.ActivityType = activityType?.ToString()?.Trim();
if (writeParams.Values.TryGetValue("CreationSource", out var creationSourceObj))
if (Enum.TryParse(creationSourceObj.ToString(), out cF4SDTicket.enumTicketCreationSource creationSource))
@@ -2193,7 +1742,7 @@ namespace C4IT.FASD.Cockpit.Communication
{
cCockpitConfiguration.Instance = new cCockpitConfiguration();
cCockpitConfiguration.Instance.agentApiConfiguration = new cAgentApiConfiguration() { ApiUrl = "", ClientId = "", ClientSecret = "", LogonUrl = "", OrganizationCode = 0 };
cCockpitConfiguration.Instance.m42ServerConfiguration = new cM42ServerConfiguration() { Server = ResolveDemoM42Server() };
cCockpitConfiguration.Instance.m42ServerConfiguration = new cM42ServerConfiguration() { Server = "https://srvwsm001.imagoverum.com" };
cCockpitConfiguration.Instance.GlobalConfig = null;
await Task.CompletedTask;
return true;