using Microsoft.Extensions.Logging; #if WorkItems using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; #endif namespace File_Folder_Helper.ADO2024.PI4; internal static partial class Helper20241108 { #if WorkItems private record Attribute([property: JsonPropertyName("isLocked")] bool IsLocked, [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("parameterTitle")] string? ParameterTitle, [property: JsonPropertyName("state")] string? State, [property: JsonPropertyName("workItemType")] string? WorkItemType); private record Relation([property: JsonPropertyName("attributes")] Attribute Attributes, [property: JsonPropertyName("id")] int Id, [property: JsonPropertyName("rel")] string Rel); private record WorkItem(DateTime? ActivatedDate, string AreaPath, string? AssignedTo, long? BusinessValue, DateTime ChangedDate, DateTime? ClosedDate, int CommentCount, DateTime CreatedDate, string Description, long? Effort, int Id, string IterationPath, int? Parent, int? Priority, Relation[]? Relations, string? Requester, DateTime? ResolvedDate, int Revision, long? RiskReductionMinusOpportunityEnablement, DateTime? StartDate, string State, string Tags, DateTime? TargetDate, long? TimeCriticality, string Title, string? Violation, long? WeightedShortestJobFirst, string WorkItemType) { public override string ToString() => $"{Id} - {WorkItemType} - {Title}"; public static WorkItem Get(WorkItem workItem, Relation[] relations) { WorkItem result = new(workItem.ActivatedDate, workItem.AreaPath, workItem.AssignedTo, workItem.BusinessValue, workItem.ChangedDate, workItem.ClosedDate, workItem.CommentCount, workItem.CreatedDate, workItem.Description, workItem.Effort, workItem.Id, workItem.IterationPath, workItem.Parent, workItem.Priority, relations, workItem.Requester, workItem.ResolvedDate, workItem.Revision, workItem.RiskReductionMinusOpportunityEnablement, workItem.StartDate, workItem.State, workItem.Tags, workItem.TargetDate, workItem.TimeCriticality, workItem.Title, workItem.Violation, workItem.WeightedShortestJobFirst, workItem.WorkItemType); return result; } public static WorkItem? GetWithOutRelations(WorkItem? workItem) { WorkItem? result = workItem is null ? null : new(workItem.ActivatedDate, workItem.AreaPath, workItem.AssignedTo, workItem.BusinessValue, workItem.ChangedDate, workItem.ClosedDate, workItem.CommentCount, workItem.CreatedDate, workItem.Description, workItem.Effort, workItem.Id, workItem.IterationPath, workItem.Parent, workItem.Priority, Array.Empty(), workItem.Requester, workItem.ResolvedDate, workItem.Revision, workItem.RiskReductionMinusOpportunityEnablement, workItem.StartDate, workItem.State, workItem.Tags, workItem.TargetDate, workItem.TimeCriticality, workItem.Title, workItem.Violation, workItem.WeightedShortestJobFirst, workItem.WorkItemType); return result; } } [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WorkItem))] private partial class WorkItemSourceGenerationContext : JsonSerializerContext { } [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WorkItem[]))] private partial class WorkItemCollectionSourceGenerationContext : JsonSerializerContext { } private record Record(WorkItem WorkItem, WorkItem? Parent, Record[]? Children, Record[]? Related, Record[]? Successors) { internal static Record GetWithoutNesting(Record record, string? violation) { Record result; WorkItem workItem = new(record.WorkItem.ActivatedDate, record.WorkItem.AreaPath, record.WorkItem.AssignedTo, record.WorkItem.BusinessValue, record.WorkItem.ChangedDate, record.WorkItem.ClosedDate, record.WorkItem.CommentCount, record.WorkItem.CreatedDate, record.WorkItem.Description, record.WorkItem.Effort, record.WorkItem.Id, record.WorkItem.IterationPath, record.WorkItem.Parent, record.WorkItem.Priority, record.WorkItem.Relations, record.WorkItem.Requester, record.WorkItem.ResolvedDate, record.WorkItem.Revision, record.WorkItem.RiskReductionMinusOpportunityEnablement, record.WorkItem.StartDate, record.WorkItem.State, record.WorkItem.Tags, record.WorkItem.TargetDate, record.WorkItem.TimeCriticality, record.WorkItem.Title, record.WorkItem.Violation is null ? violation : record.WorkItem.Violation, record.WorkItem.WeightedShortestJobFirst, record.WorkItem.WorkItemType); result = new(workItem, record.Parent, null, null, null); return result; } private static Record Get(Record record, bool keepRelations) { Record result; Record[]? childRecords; Record[]? relatedRecords; Record[]? successorRecords; List relationRecords; WorkItem? parentWorkItem = keepRelations ? record.Parent : WorkItem.GetWithOutRelations(record.Parent); WorkItem? workItem = keepRelations ? record.WorkItem : WorkItem.GetWithOutRelations(record.WorkItem) ?? throw new Exception(); if (record.Children is null) childRecords = null; else { relationRecords = []; foreach (Record r in record.Children) relationRecords.Add(Get(r, keepRelations)); childRecords = relationRecords.ToArray(); } if (record.Related is null) relatedRecords = null; else { relationRecords = []; foreach (Record r in record.Related) relationRecords.Add(Get(r, keepRelations)); relatedRecords = relationRecords.ToArray(); } if (record.Successors is null) successorRecords = null; else { relationRecords = []; foreach (Record r in record.Successors) relationRecords.Add(Get(r, keepRelations)); successorRecords = relationRecords.ToArray(); } result = new(workItem, parentWorkItem, childRecords, relatedRecords, successorRecords); return result; } internal static Record Get(WorkItem workItem, WorkItem? parent, ReadOnlyCollection? children, ReadOnlyCollection? related, ReadOnlyCollection? successors, bool keepRelations) { Record result; Record record = new(workItem, parent, children?.ToArray(), related?.ToArray(), successors?.ToArray()); result = Get(record, keepRelations); return result; } internal static ReadOnlyCollection GetKeyValuePairs(ReadOnlyDictionary keyValuePairs, WorkItem workItem, string relationName, List nests, bool keepRelations) { List results = []; Record record; nests.Add(true); WorkItem? parentWorkItem; WorkItem? relationWorkItem; List collection = []; ReadOnlyCollection? childRecords; ReadOnlyCollection? relatedRecords; ReadOnlyCollection? successorRecords; if (workItem.Relations is not null && workItem.Relations.Length > 0) { collection.Clear(); foreach (Relation relation in workItem.Relations) { if (relation.Attributes.Name != relationName) continue; if (workItem.Parent is not null && relation.Id == workItem.Parent.Value) continue; if (!keyValuePairs.TryGetValue(relation.Id, out relationWorkItem)) continue; collection.Add(relationWorkItem); } collection = (from l in collection orderby l.State != "Closed", l.Id select l).ToList(); foreach (WorkItem w in collection) { if (nests.Count > 500) break; if (w.Parent is null) parentWorkItem = null; else _ = keyValuePairs.TryGetValue(w.Parent.Value, out parentWorkItem); childRecords = GetKeyValuePairs(keyValuePairs, w, "Child", nests, keepRelations); // Forward relatedRecords = null; // GetKeyValuePairs(keyValuePairs, w, "Related", nests, keepRelations); // Related successorRecords = null; // GetKeyValuePairs(keyValuePairs, w, "Successor", nests, keepRelations); // Forward // predecessorRecords = GetKeyValuePairs(keyValuePairs, w, "Predecessor", nests, keepRelations); // Reverse record = Get(w, parentWorkItem, childRecords, relatedRecords, successorRecords, keepRelations); results.Add(record); } } return new(results); } } [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Record[]))] private partial class RecordCollectionBCommonSourceGenerationContext : JsonSerializerContext { } private static ReadOnlyDictionary GetKeyValuePairs(ReadOnlyDictionary keyValuePairs, bool keepRelations) { Dictionary results = []; Record record; List nests = []; WorkItem? parentWorkItem; ReadOnlyCollection childRecords; ReadOnlyCollection relatedRecords; ReadOnlyCollection successorRecords; foreach (KeyValuePair keyValuePair in keyValuePairs) { nests.Clear(); if (keyValuePair.Value.Parent is null) parentWorkItem = null; else _ = keyValuePairs.TryGetValue(keyValuePair.Value.Parent.Value, out parentWorkItem); try { childRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Child", nests, keepRelations); // Forward // records = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Predecessor", nests, keepRelations); // Reverse relatedRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Related", nests, keepRelations); // Related successorRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Successor", nests, keepRelations); // Forward record = Record.Get(keyValuePair.Value, parentWorkItem, childRecords, relatedRecords, successorRecords, keepRelations); } catch (Exception) { record = new(keyValuePair.Value, parentWorkItem, [], [], []); } results.Add(keyValuePair.Key, record); } return new(results); } private static ReadOnlyDictionary GetWorkItems(ReadOnlyCollection workItems, bool keepRelations) { ReadOnlyDictionary results; Dictionary keyValuePairs = []; foreach (WorkItem workItem in workItems) keyValuePairs.Add(workItem.Id, workItem); results = GetKeyValuePairs(new(keyValuePairs), keepRelations); return results; } private static ReadOnlyCollection? GetWorkItems(string fileName, string sourceDirectory) { WorkItem[]? results; string? checkFile = null; string? checkDirectory = sourceDirectory; string? pathRoot = Path.GetPathRoot(sourceDirectory); for (int i = 0; i < int.MaxValue; i++) { checkDirectory = Path.GetDirectoryName(checkDirectory); if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot) break; checkFile = Path.Combine(checkDirectory, fileName); if (File.Exists(checkFile)) break; checkFile = null; } if (checkFile is null) results = null; else { string json = File.ReadAllText(checkFile); results = JsonSerializer.Deserialize(json, WorkItemCollectionSourceGenerationContext.Default.WorkItemArray); } return results is null ? null : new(results); } internal static void WriteMarkdown(ILogger logger, List args) { // string url = args[5]; bool keepRelations = true; string fileName = args[2]; string sourceDirectory = Path.GetFullPath(args[0]); string sourceDirectoryName = Path.GetFileName(sourceDirectory); string idCheck = sourceDirectoryName.Split('-', StringSplitOptions.None)[0]; if (!int.TryParse(idCheck, out int id)) logger.LogInformation("Not valid directory!"); else { ReadOnlyCollection? workItems = GetWorkItems(fileName, sourceDirectory); if (workItems is null) logger.LogInformation("No file found!"); else { Record? record; ReadOnlyDictionary keyValuePairs = GetWorkItems(workItems, keepRelations); logger.LogInformation("Found {keyValuePairs}", keyValuePairs.Count); if (!keyValuePairs.TryGetValue(id, out record)) logger.LogInformation($"Id {id} not found!"); else { logger.LogInformation($"Id {id} found with title {record.WorkItem.Title}!"); } } } } #else internal static void WriteMarkdown(ILogger logger, List args) { logger.LogError("WriteMarkdown is not available in WorkItems {args[0]}", args[0]); logger.LogError("WriteMarkdown is not available in WorkItems {args[1]}", args[1]); } #endif }