using Adaptation.FileHandlers.json.WorkItems; using Adaptation.Shared; using Adaptation.Shared.Duplicator; using Adaptation.Shared.Methods; using log4net; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; namespace Adaptation.FileHandlers.Markdown; #nullable enable public class ProcessData : IProcessData { private readonly List _Details; List Shared.Properties.IProcessData.Details => _Details; private readonly ILog _Log; string IProcessData.GetCurrentReactor(IFileRead fileRead, Logistics logistics, Dictionary reactors) => throw new Exception(string.Concat("See ", nameof(WriteFiles))); Tuple> IProcessData.GetResults(IFileRead fileRead, Logistics logistics, List fileInfoCollection) => new(logistics.Logistics1[0], Array.Empty(), Array.Empty(), fileInfoCollection); private static string GetClosed(WorkItem workItem) => workItem.State != "Closed" ? "[ ]" : "[x]"; public ProcessData(IFileRead fileRead, Logistics logistics, string targetFileLocation, string url, ReadOnlyCollection workItemTypes, List fileInfoCollection) { _Details = new List(); _Log = LogManager.GetLogger(typeof(ProcessData)); if (fileRead.IsEAFHosted) WriteFiles(fileRead, logistics, url, workItemTypes, targetFileLocation, fileInfoCollection); } private static void WriteFiles(IFileRead fileRead, string destinationDirectory, List fileInfoCollection, ReadOnlyCollection lines, ReadOnlyCollection records, string fileName) { string markdown = string.Join(Environment.NewLine, lines); string markdownFile = Path.Combine(destinationDirectory, $"{fileName}.md"); string markdownOld = !File.Exists(markdownFile) ? string.Empty : File.ReadAllText(markdownFile); if (markdown != markdownOld) File.WriteAllText(markdownFile, markdown); if (!fileRead.IsEAFHosted) fileInfoCollection.Add(new(markdownFile)); string html = CommonMark.CommonMarkConverter.Convert(markdown).Replace(" workItemTypes, string destinationDirectory, List fileInfoCollection) { if (!Directory.Exists(destinationDirectory)) _ = Directory.CreateDirectory(destinationDirectory); string json = File.ReadAllText(logistics.ReportFullPath); // WorkItem[]? workItems = JsonSerializer.Deserialize(json); // if (workItems is null) // throw new Exception(nameof(workItems)); JsonElement[]? jsonElements = JsonSerializer.Deserialize(json); if (jsonElements is null) throw new Exception(nameof(jsonElements)); WorkItem? workItem; List workItems = new(); foreach (JsonElement jsonElement in jsonElements) { workItem = JsonSerializer.Deserialize(jsonElement.ToString()); if (workItem is null) continue; workItems.Add(workItem); } List spaces = new(); bool keepRelations = false; List lines = new(); List messages = new(); ReadOnlyCollection results; ReadOnlyDictionary keyValuePairs = GetWorkItems(workItems, keepRelations); ReadOnlyCollection records = new(keyValuePairs.Values.ToArray()); ReadOnlyCollection userStoryWorkItemTypes = new(new string[] { "User Story" }); ReadOnlyCollection bugFeatureWorkItemTypes = new(new string[] { "Bug", "Feature" }); ReadOnlyCollection bugUserStoryWorkItemTypes = new(new string[] { "Bug", "User Story" }); messages.AddRange(WriteFile(fileRead, destinationDirectory, fileInfoCollection, records, "records")); messages.AddRange(WriteWithParentsFile(fileRead, destinationDirectory, fileInfoCollection, records, bugFeatureWorkItemTypes, "bugs-features-with-parents")); messages.AddRange(WriteWithParentsFile(fileRead, destinationDirectory, fileInfoCollection, records, bugUserStoryWorkItemTypes, "bugs-user-stories-with-parents")); foreach (string workItemType in workItemTypes) { lines.Clear(); lines.Add($"# {workItemType}"); lines.Add(string.Empty); AppendLines(url, spaces, lines, records, workItemType); results = new(Array.Empty()); WriteFiles(fileRead, destinationDirectory, fileInfoCollection, new(lines), results, workItemType); _Details.Add(results); } { lines.Clear(); string workItemType = "User Story"; lines.Add($"# Total User Story Points by Site - Iteration - Assigned To (Initials)"); lines.Add(string.Empty); results = UserStoryCheckIterationPath228385(url, lines, userStoryWorkItemTypes, keyValuePairs, workItemType); WriteFiles(fileRead, destinationDirectory, fileInfoCollection, new(lines), results, $"{workItemType} check 228385"); _Details.Add(results); } if (messages.Count > 0) throw new Exception($"{messages.Count}{Environment.NewLine}{string.Join(Environment.NewLine, messages)}"); } private static ReadOnlyDictionary GetWorkItems(IEnumerable workItems, bool keepRelations) { ReadOnlyDictionary results; Dictionary keyValuePairs = new(); foreach (WorkItem workItem in workItems) keyValuePairs.Add(workItem.Id, workItem); results = GetKeyValuePairs(new(keyValuePairs), keepRelations); return results; } private static ReadOnlyDictionary GetKeyValuePairs(ReadOnlyDictionary keyValuePairs, bool keepRelations) { Dictionary results = new(); Record record; List nests = new(); 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 relatedRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Related", nests, keepRelations); // Related successorRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Successor", nests, keepRelations); // Forward // predecessorRecords = Record.GetKeyValuePairs(keyValuePairs, keyValuePair.Value, "Predecessor", nests, keepRelations); // Reverse record = Record.Get(keyValuePair.Value, parentWorkItem, childRecords, relatedRecords, successorRecords, keepRelations); } catch (Exception) { Dictionary? tag = null; record = new(keyValuePair.Value, parentWorkItem, Array.Empty(), Array.Empty(), Array.Empty(), tag); } results.Add(keyValuePair.Key, record); } return new(results); } private static ReadOnlyCollection WriteFile(IFileRead fileRead, string destinationDirectory, List fileInfoCollection, ReadOnlyCollection records, string fileName) { List results = new(); string? json = GetJson(records, results); string jsonFile = Path.Combine(destinationDirectory, $"{fileName}.json"); string jsonOld = !File.Exists(jsonFile) ? string.Empty : File.ReadAllText(jsonFile); if (!string.IsNullOrEmpty(json) && json != jsonOld) File.WriteAllText(jsonFile, json); if (!fileRead.IsEAFHosted) fileInfoCollection.Add(new(jsonFile)); return new(results); } private static ReadOnlyCollection WriteWithParentsFile(IFileRead fileRead, string destinationDirectory, List fileInfoCollection, ReadOnlyCollection records, ReadOnlyCollection workItemTypes, string fileName) { List results = new(); Record record; List filtered = new(); foreach (Record r in records) { if (r.WorkItem.State == "Removed" || !workItemTypes.Contains(r.WorkItem.WorkItemType)) continue; record = new(r.WorkItem, r.Parent, Array.Empty(), Array.Empty(), Array.Empty(), r.Tag); filtered.Add(record); } string? json = GetJson(filtered, results); string jsonFile = Path.Combine(destinationDirectory, $"{fileName}.json"); string jsonOld = !File.Exists(jsonFile) ? string.Empty : File.ReadAllText(jsonFile); if (!string.IsNullOrEmpty(json) && json != jsonOld) File.WriteAllText(jsonFile, json); if (!fileRead.IsEAFHosted) fileInfoCollection.Add(new(jsonFile)); return new(results); } private static string? GetJson(IEnumerable records, List results) { string? result; try { result = JsonSerializer.Serialize(records.ToArray(), RecordCollectionSourceGenerationContext.Default.RecordArray); } catch (Exception) { result = null; foreach (Record record in records) { try { _ = JsonSerializer.Serialize(record, RecordSourceGenerationContext.Default.Record); } catch (Exception ex) { results.Add($"Record {record.WorkItem.Id} is not serializable!{Environment.NewLine}{ex.Message}"); } } } return result; } private static void AppendLines(List spaces, List lines, Record record, bool condensed, bool sprintOnly) { string line; spaces.Add('\t'); WorkItem workItem; if (record.Children is not null) { foreach (Record child in record.Children) { workItem = child.WorkItem; line = GetLine(spaces, workItem, child, condensed, sprintOnly).TrimEnd(); lines.Add(line); AppendLines(spaces, lines, child, condensed, sprintOnly); } } spaces.RemoveAt(0); } private static void AppendLines(string url, List spaces, List lines, ReadOnlyCollection records, string workItemType) { List results = new(); string? maxIterationPath; List distinct = new(); foreach (Record record in records) { // if (record.WorkItem.Id != 109724) // continue; if (record.WorkItem.WorkItemType != workItemType) continue; results.Add($"## {record.WorkItem.AssignedTo} - {record.WorkItem.Id} - {record.WorkItem.Title}"); results.Add(string.Empty); results.Add($"- [{record.WorkItem.Id}]({url}{record.WorkItem.Id})"); if (record.Children is null || record.Children.Length == 0) results.Add(string.Empty); else { AppendLines(spaces, results, record, condensed: true, sprintOnly: false); results.Add(string.Empty); distinct.Clear(); AppendLines(spaces, distinct, record, condensed: false, sprintOnly: true); if (distinct.Count > 1) { results.Add($"## Distinct Iteration Path(s) - {record.WorkItem.WorkItemType} - {record.WorkItem.AssignedTo} - {record.WorkItem.Id} - {record.WorkItem.Title} - {record.WorkItem.IterationPath}"); results.Add(string.Empty); results.Add($"- [{record.WorkItem.Id}]({url}{record.WorkItem.Id})"); distinct.Sort(); distinct = (from l in distinct select l.Trim()).Distinct().ToList(); results.AddRange(distinct); results.Add(string.Empty); maxIterationPath = distinct.Max(); if (!string.IsNullOrEmpty(maxIterationPath) && maxIterationPath.Contains("] ") && maxIterationPath.Split(']')[1].Trim() != record.WorkItem.IterationPath) { results.Add($"### Sync to Distinct Max Iteration Path => {maxIterationPath} - {record.WorkItem.Id} - {record.WorkItem.Title}"); results.Add(string.Empty); } } results.Add($"## Extended - {record.WorkItem.Id} - {record.WorkItem.Title}"); results.Add(string.Empty); AppendLines(spaces, results, record, condensed: false, sprintOnly: false); results.Add(string.Empty); } lines.AddRange(results); results.Clear(); } } private static string GetLine(List spaces, WorkItem workItem, Record record, bool condensed, bool sprintOnly) { string result; string closed = GetClosed(workItem); result = sprintOnly ? $"\t- [ ] {workItem.IterationPath}" : condensed ? $"{new string(spaces.Skip(1).ToArray())}- {closed} {record.WorkItem.Id} - {workItem.Title}" : $"{new string(spaces.Skip(1).ToArray())}- {closed} {record.WorkItem.Id} - {workItem.Title} ~~~ {workItem.AssignedTo} - {workItem.IterationPath.Replace('\\', '-')} - {workItem.CreatedDate} --- {workItem.ClosedDate}"; return result; } private static ReadOnlyCollection UserStoryCheckIterationPath228385(string url, List lines, ReadOnlyCollection _, ReadOnlyDictionary keyValuePairs, string workItemType) { List results = new(); long totalStoryPoints; List collection = new(); ReadOnlyDictionary> records = GetWorkItemsMatching228385(keyValuePairs, workItemType); lines.Add(""); lines.Add($""); foreach (KeyValuePair> keyValuePair in records) { totalStoryPoints = 0; foreach (Record record in keyValuePair.Value) { if (record.WorkItem.StoryPoints is null) continue; totalStoryPoints += record.WorkItem.StoryPoints.Value; } collection.Add(totalStoryPoints); } lines.Add($""); lines.Add("
{string.Join("", records.Select(l => l.Key))}
{string.Join("", collection)}
"); lines.Add(string.Empty); foreach (KeyValuePair> keyValuePair in records) { totalStoryPoints = 0; foreach (Record record in keyValuePair.Value) { if (record.WorkItem.StoryPoints is null) continue; totalStoryPoints += record.WorkItem.StoryPoints.Value; } lines.Add(string.Empty); lines.Add($"## {keyValuePair.Key} => {totalStoryPoints}"); lines.Add(string.Empty); foreach (Record record in keyValuePair.Value) lines.Add($"- [ ] [{record.WorkItem.Id}]({url}{record.WorkItem.Id}) - {record.WorkItem.Title}"); } return new(results); } private static ReadOnlyDictionary> GetWorkItemsMatching228385(ReadOnlyDictionary keyValuePairs, string workItemType) { ReadOnlyDictionary> results; Record record; List records = new(); foreach (KeyValuePair keyValuePair in keyValuePairs) { record = keyValuePair.Value; if (record.WorkItem.State is "Removed" or "Closed") continue; if (!record.WorkItem.IterationPath.Contains('\\')) continue; if (record.WorkItem.StoryPoints is null) continue; if (record.WorkItem.WorkItemType != workItemType) continue; records.Add(record); } Record[] sorted = (from l in records orderby l.WorkItem.AreaPath, l.WorkItem.IterationPath, l.WorkItem.AssignedTo select l).ToArray(); results = GetWorkItemsMatching228385(new(sorted)); return results; } private static ReadOnlyDictionary> GetWorkItemsMatching228385(ReadOnlyCollection records) { Dictionary> results = new(); string key; string[] segments; List? collection; foreach (Record record in records) { key = $"{record.WorkItem.AreaPath.Split('\\').Last()}-{record.WorkItem.IterationPath.Split('\\').Last().Split(' ').Last()}"; if (!results.TryGetValue(key, out collection)) { results.Add(key, new()); if (!results.TryGetValue(key, out collection)) throw new Exception(); } collection.Add(record); } foreach (Record record in records) { if (string.IsNullOrEmpty(record.WorkItem.AssignedTo)) continue; segments = record.WorkItem.AssignedTo.Split(' '); if (segments.Length < 3) continue; key = $"{record.WorkItem.IterationPath.Split('\\').Last().Split(' ').Last()}-{segments[0][0]}{segments[1][0]}"; if (!results.TryGetValue(key, out collection)) { results.Add(key, new()); if (!results.TryGetValue(key, out collection)) throw new Exception(); } collection.Add(record); } return new(results); } internal static List GetDescriptions(JsonElement[] jsonElements) { List results = new(); Description? description; JsonSerializerOptions jsonSerializerOptions = new() { NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString }; foreach (JsonElement jsonElement in jsonElements) { if (jsonElement.ValueKind != JsonValueKind.Object) throw new Exception(); description = JsonSerializer.Deserialize(jsonElement.ToString(), jsonSerializerOptions); if (description is null) continue; results.Add(description); } return results; } }