415 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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<object> _Details;
 | |
| 
 | |
|     List<object> Shared.Properties.IProcessData.Details => _Details;
 | |
| 
 | |
|     private readonly ILog _Log;
 | |
| 
 | |
|     string IProcessData.GetCurrentReactor(IFileRead fileRead, Logistics logistics, Dictionary<string, string> reactors) =>
 | |
|         throw new Exception(string.Concat("See ", nameof(WriteFiles)));
 | |
| 
 | |
|     Tuple<string, Test[], JsonElement[], List<FileInfo>> IProcessData.GetResults(IFileRead fileRead, Logistics logistics, List<FileInfo> fileInfoCollection) =>
 | |
|         new(logistics.Logistics1[0], Array.Empty<Test>(), Array.Empty<JsonElement>(), fileInfoCollection);
 | |
| 
 | |
|     private static string GetClosed(WorkItem workItem) =>
 | |
|         workItem.State != "Closed" ? "[ ]" : "[x]";
 | |
| 
 | |
|     public ProcessData(IFileRead fileRead, Logistics logistics, string targetFileLocation, string url, ReadOnlyCollection<string> workItemTypes, List<FileInfo> fileInfoCollection)
 | |
|     {
 | |
|         _Details = new List<object>();
 | |
|         _Log = LogManager.GetLogger(typeof(ProcessData));
 | |
|         if (fileRead.IsEAFHosted)
 | |
|             WriteFiles(fileRead, logistics, url, workItemTypes, targetFileLocation, fileInfoCollection);
 | |
|     }
 | |
| 
 | |
|     private static void WriteFiles(IFileRead fileRead, string destinationDirectory, List<FileInfo> fileInfoCollection, ReadOnlyCollection<string> lines, ReadOnlyCollection<Record> 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("<a href", "<a target='_blank' href");
 | |
|         string htmlFile = Path.Combine(destinationDirectory, $"{fileName}.html");
 | |
|         string htmlOld = !File.Exists(htmlFile) ? string.Empty : File.ReadAllText(htmlFile);
 | |
|         if (html != htmlOld)
 | |
|             File.WriteAllText(htmlFile, html);
 | |
|         if (!fileRead.IsEAFHosted)
 | |
|             fileInfoCollection.Add(new(htmlFile));
 | |
|         string json = JsonSerializer.Serialize(records, new JsonSerializerOptions() { WriteIndented = true });
 | |
|         string jsonFile = Path.Combine(destinationDirectory, $"{fileName}.json");
 | |
|         string jsonOld = !File.Exists(jsonFile) ? string.Empty : File.ReadAllText(jsonFile);
 | |
|         if (json != jsonOld)
 | |
|             File.WriteAllText(jsonFile, json);
 | |
|         if (!fileRead.IsEAFHosted)
 | |
|             fileInfoCollection.Add(new(jsonFile));
 | |
|     }
 | |
| 
 | |
|     private void WriteFiles(IFileRead fileRead, Logistics logistics, string url, ReadOnlyCollection<string> workItemTypes, string destinationDirectory, List<FileInfo> fileInfoCollection)
 | |
|     {
 | |
|         if (!Directory.Exists(destinationDirectory))
 | |
|             _ = Directory.CreateDirectory(destinationDirectory);
 | |
|         string json = File.ReadAllText(logistics.ReportFullPath);
 | |
|         // WorkItem[]? workItems = JsonSerializer.Deserialize<WorkItem[]>(json);
 | |
|         // if (workItems is null)
 | |
|         //     throw new Exception(nameof(workItems));
 | |
|         JsonElement[]? jsonElements = JsonSerializer.Deserialize<JsonElement[]>(json);
 | |
|         if (jsonElements is null)
 | |
|             throw new Exception(nameof(jsonElements));
 | |
|         WorkItem? workItem;
 | |
|         List<WorkItem> workItems = new();
 | |
|         foreach (JsonElement jsonElement in jsonElements)
 | |
|         {
 | |
|             workItem = JsonSerializer.Deserialize<WorkItem>(jsonElement.ToString());
 | |
|             if (workItem is null)
 | |
|                 continue;
 | |
|             workItems.Add(workItem);
 | |
|         }
 | |
|         List<char> spaces = new();
 | |
|         bool keepRelations = false;
 | |
|         List<string> lines = new();
 | |
|         List<string> messages = new();
 | |
|         ReadOnlyCollection<Record> results;
 | |
|         ReadOnlyDictionary<int, Record> keyValuePairs = GetWorkItems(workItems, keepRelations);
 | |
|         ReadOnlyCollection<Record> records = new(keyValuePairs.Values.ToArray());
 | |
|         ReadOnlyCollection<string> userStoryWorkItemTypes = new(new string[] { "User Story" });
 | |
|         ReadOnlyCollection<string> bugFeatureWorkItemTypes = new(new string[] { "Bug", "Feature" });
 | |
|         ReadOnlyCollection<string> 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<Record>());
 | |
|             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<int, Record> GetWorkItems(IEnumerable<WorkItem> workItems, bool keepRelations)
 | |
|     {
 | |
|         ReadOnlyDictionary<int, Record> results;
 | |
|         Dictionary<int, WorkItem> keyValuePairs = new();
 | |
|         foreach (WorkItem workItem in workItems)
 | |
|             keyValuePairs.Add(workItem.Id, workItem);
 | |
|         results = GetKeyValuePairs(new(keyValuePairs), keepRelations);
 | |
|         return results;
 | |
|     }
 | |
| 
 | |
|     private static ReadOnlyDictionary<int, Record> GetKeyValuePairs(ReadOnlyDictionary<int, WorkItem> keyValuePairs, bool keepRelations)
 | |
|     {
 | |
|         Dictionary<int, Record> results = new();
 | |
|         Record record;
 | |
|         List<bool> nests = new();
 | |
|         WorkItem? parentWorkItem;
 | |
|         ReadOnlyCollection<Record> childRecords;
 | |
|         ReadOnlyCollection<Record> relatedRecords;
 | |
|         ReadOnlyCollection<Record> successorRecords;
 | |
|         foreach (KeyValuePair<int, WorkItem> 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<string, string>? tag = null;
 | |
|                 record = new(keyValuePair.Value, parentWorkItem, Array.Empty<Record>(), Array.Empty<Record>(), Array.Empty<Record>(), tag);
 | |
|             }
 | |
|             results.Add(keyValuePair.Key, record);
 | |
|         }
 | |
|         return new(results);
 | |
|     }
 | |
| 
 | |
|     private static ReadOnlyCollection<string> WriteFile(IFileRead fileRead, string destinationDirectory, List<FileInfo> fileInfoCollection, ReadOnlyCollection<Record> records, string fileName)
 | |
|     {
 | |
|         List<string> 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<string> WriteWithParentsFile(IFileRead fileRead, string destinationDirectory, List<FileInfo> fileInfoCollection, ReadOnlyCollection<Record> records, ReadOnlyCollection<string> workItemTypes, string fileName)
 | |
|     {
 | |
|         List<string> results = new();
 | |
|         Record record;
 | |
|         List<Record> 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<Record>(), Array.Empty<Record>(), Array.Empty<Record>(), 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<Record> records, List<string> 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<char> spaces, List<string> 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<char> spaces, List<string> lines, ReadOnlyCollection<Record> records, string workItemType)
 | |
|     {
 | |
|         List<string> results = new();
 | |
|         string? maxIterationPath;
 | |
|         List<string> 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<char> 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<Record> UserStoryCheckIterationPath228385(string url, List<string> lines, ReadOnlyCollection<string> _, ReadOnlyDictionary<int, Record> keyValuePairs, string workItemType)
 | |
|     {
 | |
|         List<Record> results = new();
 | |
|         long totalStoryPoints;
 | |
|         List<long> collection = new();
 | |
|         ReadOnlyDictionary<string, List<Record>> records = GetWorkItemsMatching228385(keyValuePairs, workItemType);
 | |
|         lines.Add("<table border='1'>");
 | |
|         lines.Add($"<tr><td>{string.Join("</td><td>", records.Select(l => l.Key))}</td></tr>");
 | |
|         foreach (KeyValuePair<string, List<Record>> 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($"<tr><td>{string.Join("</td><td>", collection)}</td></tr>");
 | |
|         lines.Add("</table>");
 | |
|         lines.Add(string.Empty);
 | |
|         foreach (KeyValuePair<string, List<Record>> 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<string, List<Record>> GetWorkItemsMatching228385(ReadOnlyDictionary<int, Record> keyValuePairs, string workItemType)
 | |
|     {
 | |
|         ReadOnlyDictionary<string, List<Record>> results;
 | |
|         Record record;
 | |
|         List<Record> records = new();
 | |
|         foreach (KeyValuePair<int, Record> 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<string, List<Record>> GetWorkItemsMatching228385(ReadOnlyCollection<Record> records)
 | |
|     {
 | |
|         Dictionary<string, List<Record>> results = new();
 | |
|         string key;
 | |
|         string[] segments;
 | |
|         List<Record>? 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<Description> GetDescriptions(JsonElement[] jsonElements)
 | |
|     {
 | |
|         List<Description> 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<Description>(jsonElement.ToString(), jsonSerializerOptions);
 | |
|             if (description is null)
 | |
|                 continue;
 | |
|             results.Add(description);
 | |
|         }
 | |
|         return results;
 | |
|     }
 | |
| 
 | |
| } |