using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; namespace File_Folder_Helper.Day.Q32024; internal static partial class Helper20240911 { public record Attribute([property: JsonPropertyName("isLocked")] bool IsLocked, [property: JsonPropertyName("name")] string Name); public record Relation([property: JsonPropertyName("rel")] string Type, [property: JsonPropertyName("url")] string URL, [property: JsonPropertyName("attributes")] Attribute Attributes); public record Record(WorkItem WorkItem, ReadOnlyDictionary Children); public record WorkItem(string AreaPath, string? AssignedTo, int? BusinessValue, DateTime ChangedDate, DateTime? ClosedDate, int CommentCount, DateTime CreatedDate, string Description, float? Effort, int Id, string IterationPath, int? Parent, int? Priority, Relation[] Relations, string? Requester, DateTime? ResolvedDate, int Revision, int? RiskReductionMinusOpportunityEnablement, DateTime? StartDate, string State, string Tags, DateTime? TargetDate, float? TimeCriticality, string Title, string WorkItemType, float? WeightedShortestJobFirst); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(WorkItem))] internal partial class WorkItemSourceGenerationContext : JsonSerializerContext { } private static ReadOnlyDictionary GetWorkItems(string filterDirectory, string[] files) { Dictionary results = []; string json; WorkItem? workItem; string? directoryName; foreach (string file in files) { directoryName = Path.GetDirectoryName(file); if (string.IsNullOrEmpty(directoryName)) continue; if (!directoryName.EndsWith(filterDirectory)) continue; json = File.ReadAllText(file); workItem = JsonSerializer.Deserialize(json, WorkItemSourceGenerationContext.Default.WorkItem); if (workItem is null) continue; results.Add(workItem.Id, workItem); } return new(results); } private static int? GetIdFromUrlIfChild(Relation relation) { int? result; string[] segments = relation?.Attributes is null || relation.Attributes.Name != "Child" ? [] : relation.URL.Split('/'); if (segments.Length < 2) result = null; else { if (!int.TryParse(segments[^1], out int id)) result = null; else result = id; } return result; } private static Dictionary GetKeyValuePairs(ReadOnlyDictionary workItems, WorkItem workItem) { Dictionary results = []; int? childId; WorkItem? childWorkItem; List collection = []; Dictionary keyValuePairs; if (workItem.Relations is not null && workItem.Relations.Length > 0) { collection.Clear(); foreach (Relation relation in workItem.Relations) { childId = GetIdFromUrlIfChild(relation); if (childId is null || !workItems.TryGetValue(childId.Value, out childWorkItem)) continue; collection.Add(childWorkItem); } collection = (from l in collection orderby l.State != "Closed", l.Id select l).ToList(); foreach (WorkItem item in collection) { keyValuePairs = GetKeyValuePairs(workItems, item); results.Add(item.Id, new(item, new(keyValuePairs))); } } return results; } private static ReadOnlyDictionary GetWorkItemAndChildren(ReadOnlyDictionary workItems) { Dictionary results = []; Dictionary keyValuePairs; foreach (KeyValuePair keyValuePair in workItems) { // if (keyValuePair.Key != 119185) // continue; keyValuePairs = GetKeyValuePairs(workItems, keyValuePair.Value); results.Add(keyValuePair.Key, new(keyValuePair.Value, new(keyValuePairs))); } return new(results); } private static string GetClosed(WorkItem workItem) { string result = workItem.State != "Closed" ? "[ ]" : "[x]"; return result; } private static string GetLine(List spaces, WorkItem workItem, KeyValuePair keyValuePair, bool condensed, bool sprintOnly) => sprintOnly ? $"\t- [ ] {workItem.IterationPath}" : condensed ? $"{new string(spaces.Skip(1).ToArray())}- {GetClosed(workItem)} {keyValuePair.Key} - {workItem.Title}" : $"{new string(spaces.Skip(1).ToArray())}- {GetClosed(workItem)} {keyValuePair.Key} - {workItem.Title} ~~~ {workItem.AssignedTo} - {workItem.IterationPath.Replace('\\', '-')} - {workItem.CreatedDate} --- {workItem.ClosedDate}"; private static void AppendLines(List spaces, List lines, Record record, bool condensed, bool sprintOnly) { spaces.Add('\t'); WorkItem workItem; foreach (KeyValuePair keyValuePair in record.Children) { workItem = keyValuePair.Value.WorkItem; lines.Add(GetLine(spaces, workItem, keyValuePair, condensed, sprintOnly)); AppendLines(spaces, lines, keyValuePair.Value, condensed, sprintOnly); } spaces.RemoveAt(0); } private static void AppendLines(List spaces, List lines, ReadOnlyDictionary workItemAndChildren, string workItemType) { WorkItem workItem; List distinct = []; foreach (KeyValuePair keyValuePair in workItemAndChildren) { workItem = keyValuePair.Value.WorkItem; if (workItem.WorkItemType != workItemType) continue; lines.Add($"## {workItem.AssignedTo} - {workItem.Id} - {workItem.Title}"); lines.Add(string.Empty); lines.Add($"- [{workItem.Id}](https://tfs.intra.infineon.com/tfs/FactoryIntegration/ART%20SPS/_workitems/edit/{workItem.Id})"); lines.Add(string.Empty); if (keyValuePair.Value.Children.Count > 0) { AppendLines(spaces, lines, keyValuePair.Value, condensed: true, sprintOnly: false); lines.Add(string.Empty); distinct.Clear(); lines.Add($"## Distinct Iteration Path(s) - {workItem.WorkItemType} - {workItem.AssignedTo} - {workItem.Id} - {workItem.Title} - {workItem.IterationPath}"); lines.Add(string.Empty); lines.Add($"- [{workItem.Id}](https://tfs.intra.infineon.com/tfs/FactoryIntegration/ART%20SPS/_workitems/edit/{workItem.Id})"); lines.Add(string.Empty); AppendLines(spaces, distinct, keyValuePair.Value, condensed: false, sprintOnly: true); distinct.Sort(); lines.AddRange(distinct.Distinct()); lines.Add(string.Empty); lines.Add($"## Extended - {workItem.Id} - {workItem.Title}"); lines.Add(string.Empty); AppendLines(spaces, lines, keyValuePair.Value, condensed: false, sprintOnly: false); lines.Add(string.Empty); } } } internal static void WriteMarkdown(ILogger logger, List args) { string old; string html; string text; string checkFile; List spaces = []; string searchPattern = args[2]; string filterDirectory = args[3]; string[] workItemTypes = args[4].Split('|'); List lines = ["# WorkItems", string.Empty]; string sourceDirectory = Path.GetFullPath(args[0]); string destinationDirectory = Path.GetFullPath(args[5]); if (!Directory.Exists(destinationDirectory)) _ = Directory.CreateDirectory(destinationDirectory); string[] files = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.AllDirectories); logger.LogInformation("With search pattern '{SearchPattern}' found {files} file(s)", searchPattern, files.Length); ReadOnlyDictionary workItems = GetWorkItems(filterDirectory, files); logger.LogInformation("With search pattern '{SearchPattern}' found {files} workItem(s)", searchPattern, workItems.Count); ReadOnlyDictionary workItemAndChildren = GetWorkItemAndChildren(workItems); logger.LogInformation("With search pattern '{SearchPattern}' found {files} workItemAndChildren", searchPattern, workItemAndChildren.Count); if (workItemAndChildren.Count == -1) { string json = JsonSerializer.Serialize(workItemAndChildren, new JsonSerializerOptions() { WriteIndented = true }); File.WriteAllText(".json", json); } foreach (string workItemType in workItemTypes) { AppendLines(spaces, lines, workItemAndChildren, workItemType); checkFile = Path.Combine(destinationDirectory, $"{workItemType}.md"); text = string.Join(Environment.NewLine, lines); old = !File.Exists(checkFile) ? string.Empty : File.ReadAllText(checkFile); if (text != old) File.WriteAllText(checkFile, text); checkFile = Path.Combine(destinationDirectory, $"{workItemType}.html"); html = CommonMark.CommonMarkConverter.Convert(text); old = !File.Exists(checkFile) ? string.Empty : File.ReadAllText(checkFile); if (html != old) File.WriteAllText(checkFile, html); } } }