diff --git a/Adaptation/FileHandlers/json/ProcessData.cs b/Adaptation/FileHandlers/json/ProcessData.cs index bfd6753..33ae566 100644 --- a/Adaptation/FileHandlers/json/ProcessData.cs +++ b/Adaptation/FileHandlers/json/ProcessData.cs @@ -54,18 +54,18 @@ public class ProcessData : IProcessData fiBacklogMesaCollection = JsonSerializer.Deserialize(json, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); if (fiBacklogMesaCollection is null || !fiBacklogMesaCollection.Any()) throw new NullReferenceException(); - foreach (FIBacklogMesa fIBacklogMesa in fiBacklogMesaCollection) + foreach (FIBacklogMesa fiBacklogMesa in fiBacklogMesaCollection) { - if (string.IsNullOrEmpty(fIBacklogMesa.Req)) + if (string.IsNullOrEmpty(fiBacklogMesa.Req)) continue; - if (string.IsNullOrEmpty(fIBacklogMesa.Submitted)) + if (string.IsNullOrEmpty(fiBacklogMesa.Submitted)) continue; - if (string.IsNullOrEmpty(fIBacklogMesa.Requestor)) + if (string.IsNullOrEmpty(fiBacklogMesa.Requestor)) continue; - key = $"{fIBacklogMesa.Req} - "; + key = $"{fiBacklogMesa.Req} - "; if (results.ContainsKey(key)) continue; - results.Add(key, fIBacklogMesa); + results.Add(key, fiBacklogMesa); } return results; } @@ -112,18 +112,139 @@ public class ProcessData : IProcessData return results; } - private static Value[] GetExtra(IReadOnlyList workItems, Dictionary keyToFIBacklogMesa) + private static ReadOnlyCollection GetValueWithReqCollection(IReadOnlyList workItems) { - List results = new(); + List results = new(); + string[] segments; foreach (Value value in workItems) { - if (!value.Fields.SystemTitle.Contains(" - ")) + segments = value.Fields.SystemTitle.Split('-'); + if (segments.Length < 2) continue; - if (keyToFIBacklogMesa.Remove(value.Fields.SystemTitle)) + if (!int.TryParse(segments[0], out int req) || req == 0) continue; - results.Add(value); + results.Add(new(value, req)); + } + return new(results); + } + + private static ReadOnlyCollection RemoveFrom(Dictionary keyToFIBacklogMesa, ReadOnlyCollection valueWithReqCollection) + { + List results = new(); + foreach (ValueWithReq valueWithReq in valueWithReqCollection) + { + if (keyToFIBacklogMesa.Remove($"{valueWithReq.Req} - ")) + continue; + results.Add(valueWithReq); + } + return new(results); + } + + private static void Update(WorkItemTrackingHttpClient workItemTrackingHttpClient, string sync, ValueWithReq valueWithReq) + { + JsonPatchDocument result = new(); + AddPatch(result, "/fields/System.Tags", sync); + Task workItem = workItemTrackingHttpClient.UpdateWorkItemAsync(result, valueWithReq.Value.Id); + workItem.Wait(); + } + + private static DateTime? GetCommitDate(FIBacklogMesa fiBacklogMesa) + { + DateTime? result; + if (!DateTime.TryParseExact(fiBacklogMesa.CommitDate.Split(' ').First(), "MM/dd/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime) && dateTime != DateTime.MinValue) + result = null; + else + result = dateTime.AddHours(12).ToUniversalTime(); + return result; + } + + private static string? GetMappedState(FIBacklogMesa fiBacklogMesa) => + fiBacklogMesa.Status == "CMP" ? "Closed" : fiBacklogMesa.Status == "UAT" ? "Resolved" : fiBacklogMesa.Status == "In process" ? "Active" : null; + + private static void SetSyncTag(WorkItemTrackingHttpClient workItemTrackingHttpClient, ReadOnlyDictionary requestorNameToUser, Dictionary keyToFIBacklogMesa, ReadOnlyCollection valueWithReqCollection) + { + string key; + bool isBugFix; + string? state; + List tags; + TimeSpan timeSpan; + DateTime? dateTime; + List compareTags; + const string sync = "Sync"; + FIBacklogMesa? fiBacklogMesa; + foreach (ValueWithReq valueWithReq in valueWithReqCollection) + { + key = $"{valueWithReq.Req} - "; + compareTags = GetTags(valueWithReq.Value.Fields); + if (compareTags.Contains(sync)) + continue; + if (!keyToFIBacklogMesa.TryGetValue(key, out fiBacklogMesa)) + continue; + tags = GetTags(fiBacklogMesa); + isBugFix = fiBacklogMesa.Priority == "0 - BugFix"; + _ = requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser); + if (!string.IsNullOrEmpty(requestorUser) && (valueWithReq.Value.Fields.SystemAssignedTo is null || !valueWithReq.Value.Fields.SystemAssignedTo.UniqueName.Equals(requestorUser, StringComparison.CurrentCultureIgnoreCase))) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + if (valueWithReq.Value.Fields.SystemTitle != $"{valueWithReq.Req} - {fiBacklogMesa.Subject}") + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + foreach (string tag in tags) + { + if (compareTags.Contains(tag)) + continue; + _ = tags.Remove(tag); + break; + } + if (tags.Count != compareTags.Count) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + if ((isBugFix && valueWithReq.Value.Fields.SystemWorkItemType != "Bug") || (!isBugFix && valueWithReq.Value.Fields.SystemWorkItemType == "Bug")) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + if (!isBugFix) + { + if (!int.TryParse(fiBacklogMesa.Priority.Substring(0, 1), out int priority)) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + if (valueWithReq.Value.Fields.MicrosoftVSTSCommonPriority != priority) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + } + state = GetMappedState(fiBacklogMesa); + if (!string.IsNullOrEmpty(state) && valueWithReq.Value.Fields.SystemState != state) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + if (!isBugFix && int.TryParse(fiBacklogMesa.EstEffortDays, out int estEffortDays) && valueWithReq.Value.Fields.Effort != estEffortDays) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + dateTime = GetCommitDate(fiBacklogMesa); + if (dateTime is not null) + { + timeSpan = new(valueWithReq.Value.Fields.TargetDate.Ticks - dateTime.Value.Ticks); + if (timeSpan.Hours is > 32 or < -32) + { + Update(workItemTrackingHttpClient, sync, valueWithReq); + continue; + } + } } - return results.ToArray(); } private static string GetDescription(FIBacklogMesa fiBacklogMesa, DateTime dateTime) => @@ -183,15 +304,15 @@ public class ProcessData : IProcessData } } - private static JsonPatchDocument GetUATDocument(string project, ReadOnlyDictionary requestorNameToUser, DateTime dateTime, string key, FIBacklogMesa fiBacklogMesa) + private static JsonPatchDocument GetUATDocument(string project, ReadOnlyDictionary requestorNameToUser, DateTime dateTime, FIBacklogMesa fiBacklogMesa) { JsonPatchDocument result = new(); AddPatch(result, "/fields/System.AreaPath", project); string description = GetDescription(fiBacklogMesa, dateTime); AddPatch(result, "/fields/System.IterationPath", project); - AddPatch(result, "/fields/System.Title", key); + AddPatch(result, "/fields/System.Title", $"{fiBacklogMesa.Req} - UAT"); AddPatch(result, "/fields/System.Description", description); - if (requestorNameToUser.TryGetValue(fiBacklogMesa.AssignedTo, out string? requestorUser)) + if (requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser)) AddPatch(result, "/fields/System.AssignedTo", requestorUser); return result; } @@ -205,6 +326,9 @@ public class ProcessData : IProcessData AddPatch(result, "/fields/System.AreaPath", project); AddPatch(result, "/fields/System.IterationPath", project); AddPatch(result, "/fields/System.Title", $"{fiBacklogMesa.Req} - {fiBacklogMesa.Subject}"); + string? state = GetMappedState(fiBacklogMesa); + if (!string.IsNullOrEmpty(state)) + AddPatch(result, "/fields/System.State", state); if (!string.IsNullOrEmpty(fiBacklogMesa.Definition)) AddPatch(result, "/fields/System.Description", fiBacklogMesa.Definition); if (assignedToNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser)) @@ -229,8 +353,8 @@ public class ProcessData : IProcessData AddPatch(result, "/fields/System.AreaPath", project); AddPatch(result, "/fields/System.IterationPath", project); AddPatch(result, "/fields/System.Title", $"{fiBacklogMesa.Req} - Developer Task"); - if (assignedToNameToUser.TryGetValue(fiBacklogMesa.SecondResource, out string? assignedToUser)) - AddPatch(result, "/fields/System.AssignedTo", assignedToUser); + if (assignedToNameToUser.TryGetValue(fiBacklogMesa.SecondResource, out string? secondResourceUser)) + AddPatch(result, "/fields/System.AssignedTo", secondResourceUser); return result; } @@ -243,6 +367,9 @@ public class ProcessData : IProcessData AddPatch(result, "/fields/System.AreaPath", project); AddPatch(result, "/fields/System.IterationPath", project); AddPatch(result, "/fields/System.Title", $"{fiBacklogMesa.Req} - User Story"); + string? state = GetMappedState(fiBacklogMesa); + if (!string.IsNullOrEmpty(state)) + AddPatch(result, "/fields/System.State", state); if (requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser)) AddPatch(result, "/fields/System.AssignedTo", requestorUser); return result; @@ -271,6 +398,9 @@ public class ProcessData : IProcessData JsonPatchDocument result = new(); if (userStoryWorkItemTask?.Result.Id is null) throw new NotSupportedException(); + string title = $"{fiBacklogMesa.Req} - {fiBacklogMesa.Subject}"; + if (title.Length > 128) + title = title.Substring(0, 127); AddPatch(result, "/relations/-", new WorkItemRelation() { Rel = "System.LinkTypes.Hierarchy-Forward", Url = userStoryWorkItemTask.Result.Url }); AddPatch(result, "/fields/System.AreaPath", project); if (tags.Count > 0) @@ -281,38 +411,62 @@ public class ProcessData : IProcessData AddPatch(result, "/fields/System.IterationPath", project); if (!string.IsNullOrEmpty(fiBacklogMesa.Definition)) AddPatch(result, "/fields/System.Description", fiBacklogMesa.Definition); + string? state = GetMappedState(fiBacklogMesa); + if (!string.IsNullOrEmpty(state)) + AddPatch(result, "/fields/System.State", state); if (int.TryParse(fiBacklogMesa.Priority.Substring(0, 1), out int priority) && priority != 0) AddPatch(result, "/fields/Microsoft.VSTS.Common.Priority", priority); if (!string.IsNullOrEmpty(fiBacklogMesa.EstEffortDays) && int.TryParse(fiBacklogMesa.EstEffortDays, out int estEffortDays) && estEffortDays != 0) AddPatch(result, "/fields/Microsoft.VSTS.Scheduling.Effort", estEffortDays); - if (!string.IsNullOrEmpty(fiBacklogMesa.CommitDate) && DateTime.TryParseExact(fiBacklogMesa.CommitDate.Split(' ').First(), "MM/dd/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime commitDate) && commitDate != DateTime.MinValue) - AddPatch(result, "/fields/Microsoft.VSTS.Scheduling.TargetDate", commitDate.AddHours(12).ToUniversalTime()); + DateTime? dateTime = GetCommitDate(fiBacklogMesa); + if (dateTime is not null) + AddPatch(result, "/fields/Microsoft.VSTS.Scheduling.TargetDate", dateTime); if (!string.IsNullOrEmpty(fiBacklogMesa.Updates)) AddPatch(result, "/fields/System.History", fiBacklogMesa.Updates); - AddPatch(result, "/fields/System.Title", $"{fiBacklogMesa.Req} - {fiBacklogMesa.Subject}"); + AddPatch(result, "/fields/System.Title", title); if (requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser)) AddPatch(result, "/fields/System.AssignedTo", requestorUser); // https://tfs.intra.infineon.com/tfs/ManufacturingIT/Mesa_FI/_apis/wit/workitemtypes/feature/fields?api-version=7.0 return result; } - private static void DoWork(WorkItemTrackingHttpClient workItemTrackingHttpClient, string project, ReadOnlyDictionary assignedToNameToUser, ReadOnlyDictionary requestorNameToUser, string key, FIBacklogMesa fiBacklogMesa) + private static List GetTags(FIBacklogMesa fiBacklogMesa) { - DateTime dateTime; - List tags = new(); - JsonPatchDocument tagDocument; - Task? secondResourceWorkItemTask = null; - bool isBugFix = fiBacklogMesa.Priority == "0 - BugFix"; - if (!DateTime.TryParse(fiBacklogMesa.Submitted, out dateTime)) - dateTime = DateTime.MinValue; + List results = new(); foreach (string tag in fiBacklogMesa.SystemS.Split('/')) { if (string.IsNullOrEmpty(tag.Trim())) continue; - tags.Add(tag.Trim()); + results.Add(tag.Trim()); } - tags.Reverse(); - JsonPatchDocument uatDocument = GetUATDocument(project, requestorNameToUser, dateTime, key, fiBacklogMesa); + return results; + } + + private static List GetTags(Fields fields) + { + List results = new(); + if (!string.IsNullOrEmpty(fields.SystemTags)) + { + foreach (string tag in fields.SystemTags.Split(';')) + { + if (string.IsNullOrEmpty(tag.Trim())) + continue; + results.Add(tag.Trim()); + } + } + return results; + } + + private static void DoWork(WorkItemTrackingHttpClient workItemTrackingHttpClient, string project, ReadOnlyDictionary assignedToNameToUser, ReadOnlyDictionary requestorNameToUser, FIBacklogMesa fiBacklogMesa) + { + DateTime dateTime; + JsonPatchDocument tagDocument; + List tags = GetTags(fiBacklogMesa); + Task? secondResourceWorkItemTask = null; + bool isBugFix = fiBacklogMesa.Priority == "0 - BugFix"; + if (!DateTime.TryParse(fiBacklogMesa.Submitted, out dateTime)) + dateTime = DateTime.MinValue; + JsonPatchDocument uatDocument = GetUATDocument(project, requestorNameToUser, dateTime, fiBacklogMesa); Task uatWorkItemTask = workItemTrackingHttpClient.CreateWorkItemAsync(uatDocument, project, "Task"); uatWorkItemTask.Wait(); if (isBugFix) @@ -383,8 +537,10 @@ public class ProcessData : IProcessData Dictionary keyToFIBacklogMesa = GetFIBacklogMesaCollection(json); Value[] workItems = string.IsNullOrEmpty(ids) ? Array.Empty() : GetWorkItems(httpClient, basePage, api, ids); int count = keyToFIBacklogMesa.Count; - Value[] extra = GetExtra(workItems, keyToFIBacklogMesa); - if (count != extra.Length) + ReadOnlyCollection valueWithReqCollection = GetValueWithReqCollection(workItems); + SetSyncTag(workItemTrackingHttpClient, requestorNameToUser, keyToFIBacklogMesa, valueWithReqCollection); + ReadOnlyCollection extra = RemoveFrom(keyToFIBacklogMesa, valueWithReqCollection); + if (count != extra.Count) { } if (count != keyToFIBacklogMesa.Count) { } @@ -395,7 +551,7 @@ public class ProcessData : IProcessData break; if (!fileRead.IsEAFHosted) continue; - DoWork(workItemTrackingHttpClient, project, assignedToNameToUser, requestorNameToUser, keyValuePair.Key, keyValuePair.Value); + DoWork(workItemTrackingHttpClient, project, assignedToNameToUser, requestorNameToUser, keyValuePair.Value); counter++; } } diff --git a/Adaptation/FileHandlers/json/WorkItems/Fields.cs b/Adaptation/FileHandlers/json/WorkItems/Fields.cs index f7c74d6..200c90b 100644 --- a/Adaptation/FileHandlers/json/WorkItems/Fields.cs +++ b/Adaptation/FileHandlers/json/WorkItems/Fields.cs @@ -23,7 +23,9 @@ public class Fields DateTime microsoftVSTSCommonStateChangeDate, int microsoftVSTSCommonPriority, string systemDescription, - string systemTags + string systemTags, + float? effort, + DateTime targetDate ) { SystemAreaPath = systemAreaPath; @@ -43,6 +45,8 @@ public class Fields MicrosoftVSTSCommonPriority = microsoftVSTSCommonPriority; SystemDescription = systemDescription; SystemTags = systemTags; + Effort = effort; + TargetDate = targetDate; } [JsonPropertyName("System.AreaPath")] @@ -95,4 +99,10 @@ public class Fields [JsonPropertyName("System.Tags")] public string SystemTags { get; } // { init; get; } + + [JsonPropertyName("Microsoft.VSTS.Scheduling.Effort")] + public float? Effort { get; } // { init; get; } + + [JsonPropertyName("Microsoft.VSTS.Scheduling.TargetDate")] + public DateTime TargetDate { get; } // { init; get; } } \ No newline at end of file diff --git a/Adaptation/FileHandlers/json/WorkItems/ValueWithReq.cs b/Adaptation/FileHandlers/json/WorkItems/ValueWithReq.cs new file mode 100644 index 0000000..8caf3b3 --- /dev/null +++ b/Adaptation/FileHandlers/json/WorkItems/ValueWithReq.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Adaptation.FileHandlers.json.WorkItems; + +public class ValueWithReq +{ + [JsonConstructor] + public ValueWithReq( + Value value, + int req + ) + { + Value = value; + Req = req; + } + + public Value Value { get; set; } // { init; get; } + public int Req { get; set; } // { init; get; } +} \ No newline at end of file diff --git a/MESAFIBACKLOG.csproj b/MESAFIBACKLOG.csproj index 8783039..dc0e13a 100644 --- a/MESAFIBACKLOG.csproj +++ b/MESAFIBACKLOG.csproj @@ -122,6 +122,7 @@ +