aktueller Stand

This commit is contained in:
Meik
2026-02-09 19:36:39 +01:00
parent 825ddf05d4
commit d6cbbe1ef1
6 changed files with 510 additions and 186 deletions

View File

@@ -35,7 +35,6 @@ namespace C4IT.FASD.Cockpit.Communication
new Dictionary<string, Dictionary<string, List<TicketOverviewRelationDefinition>>>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<Guid> _generatedTicketIds = new HashSet<Guid>();
private readonly object _demoTicketSync = new object();
private const string DemoTicketHasDetailsInfoKey = "Demo.HasTicketDetails";
#endregion
public cFasdCockpitCommunicationDemo()
@@ -47,6 +46,7 @@ namespace C4IT.FASD.Cockpit.Communication
BuildCategoryLookup();
LoadTicketOverviewRelations();
LoadGeneratedTickets();
EnsureOverviewTicketJournalEntries();
}
public override bool IsDemo() => true;
@@ -193,11 +193,11 @@ namespace C4IT.FASD.Cockpit.Communication
}
}
private void LoadGeneratedTickets()
{
try
{
var records = TicketOverviewDataStore.LoadTickets();
private void LoadGeneratedTickets()
{
try
{
var records = TicketOverviewDataStore.LoadTickets();
foreach (var record in records)
{
AppendDemoTicket(record);
@@ -268,9 +268,112 @@ namespace C4IT.FASD.Cockpit.Communication
return;
var generatedTicket = ConvertToTicket(record);
targetSample.Tickets.Add(generatedTicket);
}
}
targetSample.Tickets.Add(generatedTicket);
EnsureTicketHasMinimumDemoJournalEntries(generatedTicket);
}
}
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)
{
@@ -378,27 +481,31 @@ namespace C4IT.FASD.Cockpit.Communication
await SimulateTicketOverviewLatencyAsync(count);
return new List<cF4sdApiSearchResultRelation>();
}
int requestedCount = count <= 0 ? definitions.Count : Math.Min(count, definitions.Count);
await SimulateTicketOverviewLatencyAsync(requestedCount);
var relations = new List<cF4sdApiSearchResultRelation>(requestedCount);
foreach (var definition in definitions.Take(requestedCount))
{
var relation = new cF4sdApiSearchResultRelation
{
Type = enumF4sdSearchResultClass.Ticket,
DisplayName = definition.DisplayName ?? string.Empty,
Name = definition.DisplayName ?? string.Empty,
int requestedCount = count <= 0 ? definitions.Count : Math.Min(count, definitions.Count);
await SimulateTicketOverviewLatencyAsync(requestedCount);
var relations = new List<cF4sdApiSearchResultRelation>(requestedCount);
foreach (var definition in definitions.Take(requestedCount))
{
var detailTicket = FindTicketForOverviewRelation(definition);
var summary = definition.Summary ?? string.Empty;
if (detailTicket != null && !string.IsNullOrWhiteSpace(detailTicket.Summary))
summary = detailTicket.Summary;
var relation = new cF4sdApiSearchResultRelation
{
Type = enumF4sdSearchResultClass.Ticket,
DisplayName = definition.DisplayName ?? string.Empty,
Name = definition.DisplayName ?? string.Empty,
id = definition.TicketId,
Status = enumF4sdSearchResultStatus.Active,
Infos = new Dictionary<string, string>
{
["Summary"] = definition.Summary ?? string.Empty,
["StatusId"] = definition.StatusId ?? string.Empty,
["UserDisplayName"] = definition.UserDisplayName ?? string.Empty,
["UserAccount"] = definition.UserAccount ?? string.Empty,
["UserDomain"] = definition.UserDomain ?? string.Empty,
[DemoTicketHasDetailsInfoKey] = HasTicketDetails(definition.TicketId).ToString()
},
Status = enumF4sdSearchResultStatus.Active,
Infos = new Dictionary<string, string>
{
["Summary"] = summary,
["StatusId"] = definition.StatusId ?? string.Empty,
["UserDisplayName"] = definition.UserDisplayName ?? string.Empty,
["UserAccount"] = definition.UserAccount ?? string.Empty,
["UserDomain"] = definition.UserDomain ?? string.Empty
},
Identities = new cF4sdIdentityList
{
new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = definition.TicketId },
@@ -416,32 +523,40 @@ namespace C4IT.FASD.Cockpit.Communication
}
finally
{
LogMethodEnd(CM);
}
}
private bool HasTicketDetails(Guid ticketId)
{
if (ticketId == Guid.Empty)
return false;
foreach (var sample in MockupData)
{
var tickets = sample?.Tickets;
if (tickets == null)
continue;
foreach (var ticket in tickets)
{
if (ticket?.Id == ticketId)
return true;
}
}
return false;
}
private static Task SimulateTicketOverviewLatencyAsync(int count)
{
LogMethodEnd(CM);
}
}
private cF4SDTicket FindTicketForOverviewRelation(TicketOverviewRelationDefinition 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)
return null;
return fallbackData.Tickets.FirstOrDefault(ticket => ticket.Id == definition.TicketId);
}
private static Task SimulateTicketOverviewLatencyAsync(int count)
{
int baseMs = 420;
int perItem = 100;
int capped = Math.Max(0, Math.Min(count, 5));
@@ -714,21 +829,115 @@ namespace C4IT.FASD.Cockpit.Communication
return output;
}
public override async Task<cFasdApiSearchResultCollection> GetUserSearchResults(string Name, List<string> SIDs)
{
var output = new cFasdApiSearchResultCollection();
try
{
foreach (var data in MockupData)
{
if (data.SampleDataName.Equals(Name, StringComparison.InvariantCultureIgnoreCase))
{
//todo: add a field in demo data
var searchResultClass = enumF4sdSearchResultClass.User;
output[data.SampleDataName] = new List<cFasdApiSearchResultEntry>() { new cFasdApiSearchResultEntry() { id = data.SampleDataId, Name = data.SampleDataName, DisplayName = data.SampleDataName, Type = searchResultClass } };
}
}
public override async Task<cFasdApiSearchResultCollection> GetUserSearchResults(string Name, List<string> SIDs)
{
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))
continue;
//todo: add a field in demo data
var searchResultClass = enumF4sdSearchResultClass.User;
if (!output.ContainsKey(data.SampleDataName))
{
output[data.SampleDataName] = new List<cFasdApiSearchResultEntry>()
{
new cFasdApiSearchResultEntry()
{
id = data.SampleDataId,
Name = data.SampleDataName,
DisplayName = data.SampleDataName,
Type = searchResultClass
}
};
}
}
await Task.CompletedTask;
}
catch (Exception E)
@@ -758,8 +967,8 @@ namespace C4IT.FASD.Cockpit.Communication
var output = new List<cF4sdApiSearchResultRelation>();
switch (resultIds.First().ToString())
{
switch (resultIds.First().ToString())
{
case constGuidOlliOffline: // Olli Offline
output.Add(new cF4sdApiSearchResultRelation() { id = Guid.NewGuid(), Name = "C4-NB005", DisplayName = "C4-NB005", Infos = new Dictionary<string, string>() { ["UserAccountType"] = "AD", ["UserAccount"] = "C4-NB005", ["UserDomain"] = "C4IT" }, LastUsed = DateTime.UtcNow.AddDays(-10), Type = enumF4sdSearchResultClass.Computer, UsingLevel = 1, Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Computer, Id = Guid.Parse(constGuidOlliOffline) }, new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Computer, Id = Guid.NewGuid() } } });
@@ -930,18 +1139,67 @@ namespace C4IT.FASD.Cockpit.Communication
case constGuidComputer: // Computer
break;
default:
output.Add(new cF4sdApiSearchResultRelation() { id = Guid.NewGuid(), Name = "C4IT-007", DisplayName = "C4IT-007", LastUsed = DateTime.UtcNow, Type = enumF4sdSearchResultClass.Computer, UsingLevel = 1, Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = resultIds.First() }, new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Computer, Id = Guid.NewGuid() } } });
break;
}
await Task.CompletedTask;
output = output.OrderByDescending(relation => relation.LastUsed).ToList();
return output;
}
public override async Task<cF4SDHealthCardRawData> GetHealthCardData(cF4sdHealthCardRawDataRequest requestData)
{
output.Add(new cF4sdApiSearchResultRelation() { id = Guid.NewGuid(), Name = "C4IT-007", DisplayName = "C4IT-007", LastUsed = DateTime.UtcNow, Type = enumF4sdSearchResultClass.Computer, UsingLevel = 1, Identities = new cF4sdIdentityList() { new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = resultIds.First() }, new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Computer, Id = Guid.NewGuid() } } });
break;
}
if (resultType == enumF4sdSearchResultClass.User)
{
foreach (var userId in resultIds)
{
AppendDemoTicketRelationsForUser(userId, output);
}
}
await Task.CompletedTask;
output = output.OrderByDescending(relation => relation.LastUsed).ToList();
return output;
}
private void AppendDemoTicketRelationsForUser(Guid userId, List<cF4sdApiSearchResultRelation> output)
{
if (userId == Guid.Empty || output == null)
return;
var selectedData = MockupData.FirstOrDefault(data => data?.SampleDataId == userId);
if (selectedData?.Tickets == null)
return;
foreach (var demoTicket in selectedData.Tickets)
{
if (demoTicket == null || demoTicket.Id == Guid.Empty)
continue;
if (output.Any(relation => relation.Type == enumF4sdSearchResultClass.Ticket && relation.id == demoTicket.Id))
continue;
output.Add(new cF4sdApiSearchResultRelation()
{
id = demoTicket.Id,
Name = demoTicket.Name,
DisplayName = demoTicket.Name,
LastUsed = demoTicket.CreationDate == default ? DateTime.UtcNow : demoTicket.CreationDate.ToUniversalTime(),
Type = enumF4sdSearchResultClass.Ticket,
UsingLevel = 1,
Infos = new Dictionary<string, string>()
{
["Summary"] = demoTicket.Summary ?? string.Empty,
["Status"] = demoTicket.Status.ToString(),
["StatusId"] = ((int)demoTicket.Status).ToString(),
["Asset"] = demoTicket.Asset ?? string.Empty
},
Identities = new cF4sdIdentityList()
{
new cF4sdIdentityEntry() { Class = enumFasdInformationClass.User, Id = userId },
new cF4sdIdentityEntry() { Class = enumFasdInformationClass.Ticket, Id = demoTicket.Id }
}
});
}
}
public override async Task<cF4SDHealthCardRawData> GetHealthCardData(cF4sdHealthCardRawDataRequest requestData)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
@@ -968,14 +1226,29 @@ namespace C4IT.FASD.Cockpit.Communication
if (selectedData is null)
return output;
output = selectedData.GetHealthCardData();
var ticketRequest = requestData.Identities.FirstOrDefault(data => data.Class is enumFasdInformationClass.Ticket);
if (ticketRequest != null)
{
var selectedTicket = selectedData.Tickets.FirstOrDefault(ticket => ticket.Id == ticketRequest.Id);
if (selectedTicket != null)
{
string ticketStatusString = string.Empty;
output = selectedData.GetHealthCardData();
var ticketRequest = requestData.Identities.FirstOrDefault(data => data.Class is enumFasdInformationClass.Ticket);
if (ticketRequest != null)
{
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;
switch (selectedTicket.Status)
{
case enumTicketStatus.Unknown: