Ready to start loading backlog

This commit is contained in:
Mike Phares 2024-08-14 13:51:57 -07:00
parent 29bec0cb9a
commit 754cc1ee2b
21 changed files with 1280 additions and 12 deletions

24
.vscode/launch.json vendored
View File

@ -13,17 +13,19 @@
"args": [
"s",
"X",
"D:/5-Other-Small/Kanban-mestsa003/FI-Backlog-Mesa-Request-List/859-FI-Backlog-Mesa-Request-List/.vscode",
"Day-Helper-2024-08-06",
"1000",
"D:/Logs",
"*.txt",
"25",
"4",
"D:/IFXApps/Logs",
"8888",
"9999"
],
"T:/MESAFIBACKLOG/06_SourceCode/MESAFIBACKLOG/Adaptation/.vscode/helper",
"Day-Helper-2024-08-09",
"MES",
"https://tfs.intra.infineon.com",
"/tfs/FactoryIntegration",
"ART SPS",
"/_apis/wit",
"/wiql/3373b300-8de3-4301-9795-e990c3b226f9",
"4n7d2jcql6bkq32f66tohddonfxajkypq66lm5y3zqemtlohawsa",
"FI Backlog Mesa - Request List.json",
"Chase|infineon\\TuckerC,Dakota(SRP)|infineon\\Mitchem,Daniel|infineon\\StieberD,Jonathan|infineon\\Ouellette,Mike|infineon\\Phares",
"Chad B|infineon\\cbecker1,Debra Q|infineon\\Quinones,Jeanne M|infineon\\jmcinty2,Jessica F|infineon\\jfuente1,Jonathon S|infineon\\jsperli1,Justin H|infineon\\jhollan2,Kelly C|infineon\\kclark1,Mark C|infineon\\mcouste1,Marti J|infineon\\mjarsey1,Nik C|infineon\\nclark1,Peyton M|infineon\\McChesne,Ron O|infineon\\Olmstead,Susan H|infineon\\HendersS,Tiffany M|infineon\\tmunoz1,Todd C|infineon\\tcarrie1"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"stopAtEntry": false

View File

@ -89,6 +89,8 @@ internal static class HelperDay
Day.Q32024.Helper20240805.RenameFiles(logger, args);
else if (args[1] == "Day-Helper-2024-08-06")
Day.Q32024.Helper20240806.ArchiveFiles(logger, args);
else if (args[1] == "Day-Helper-2024-08-09")
Day.Q32024.Helper20240809.CreateWorkItems(logger, args);
else
throw new Exception(appSettings.Company);
}

View File

@ -0,0 +1,144 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.ConvertExcelToJson;
public class FIBacklogMesa
{
[JsonConstructor]
public FIBacklogMesa(string req,
string submitted,
string requestor,
string assignedTo,
string secondResource,
string subject,
string systemS,
string priority,
string training,
string prioritySubset,
string status,
string definition,
string updates,
string estEffortDays,
string commitDate,
string reCommitDate,
string uATAsOf,
string cmpDate,
string f20,
string f21,
string f22,
string f23,
string f24,
string f25,
string f26,
string f27,
string f28,
string f29,
string f30,
string f31,
string f32,
string f33)
{
Req = req;
Submitted = submitted;
Requestor = requestor;
AssignedTo = assignedTo;
SecondResource = secondResource;
Subject = subject;
SystemS = systemS;
Priority = priority;
Training = training;
PrioritySubset = prioritySubset;
Status = status;
Definition = definition;
Updates = updates;
EstEffortDays = estEffortDays;
CommitDate = commitDate;
ReCommitDate = reCommitDate;
UATAsOf = uATAsOf;
CMPDate = cmpDate;
F20 = f20;
F21 = f21;
F22 = f22;
F23 = f23;
F24 = f24;
F25 = f25;
F26 = f26;
F27 = f27;
F28 = f28;
F29 = f29;
F30 = f30;
F31 = f31;
F32 = f32;
F33 = f33;
}
public string Req { get; set; } // { init; get; }
public string Submitted { get; set; } // { init; get; }
public string Requestor { get; set; } // { init; get; }
[JsonPropertyName("Assigned To")]
public string AssignedTo { get; set; } // { init; get; }
[JsonPropertyName("Second Resource")]
public string SecondResource { get; set; } // { init; get; }
[JsonPropertyName("Subject - from Requestor")]
public string Subject { get; set; } // { init; get; }
[JsonPropertyName("System(s)")]
public string SystemS { get; set; } // { init; get; }
public string Priority { get; set; } // { init; get; }
[JsonPropertyName("Spec/ECN/Training")]
public string Training { get; set; } // { init; get; }
[JsonPropertyName("Qual/Eff")]
public string PrioritySubset { get; set; } // { init; get; }
public string Status { get; set; } // { init; get; }
[JsonPropertyName("Definition - from FI")]
public string Definition { get; set; } // { init; get; }
public string Updates { get; set; } // { init; get; }
[JsonPropertyName("Est Effort _(days)")]
public string EstEffortDays { get; set; } // { init; get; }
[JsonPropertyName("Commit Date")]
public string CommitDate { get; set; } // { init; get; }
[JsonPropertyName("Re-Commit Date")]
public string ReCommitDate { get; set; } // { init; get; }
[JsonPropertyName("UAT as of")]
public string UATAsOf { get; set; } // { init; get; }
[JsonPropertyName("CMP _Date")]
public string CMPDate { get; set; } // { init; get; }
public string F20 { get; set; } // { init; get; }
public string F21 { get; set; } // { init; get; }
public string F22 { get; set; } // { init; get; }
public string F23 { get; set; } // { init; get; }
public string F24 { get; set; } // { init; get; }
public string F25 { get; set; } // { init; get; }
public string F26 { get; set; } // { init; get; }
public string F27 { get; set; } // { init; get; }
public string F28 { get; set; } // { init; get; }
public string F29 { get; set; } // { init; get; }
public string F30 { get; set; } // { init; get; }
public string F31 { get; set; } // { init; get; }
public string F32 { get; set; } // { init; get; }
public string F33 { get; set; } // { init; get; }
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(FIBacklogMesa[]))]
internal partial class FIBacklogMesaCollectionSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(FIBacklogMesa))]
internal partial class FIBacklogMesaSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,560 @@
using File_Folder_Helper.Day.Q32024.ConvertExcelToJson;
using File_Folder_Helper.Day.Q32024.WorkItems;
using Microsoft.Extensions.Logging;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Web;
namespace File_Folder_Helper.Day.Q32024;
internal static partial class Helper20240809
{
private static void AddPatch(JsonPatchDocument document, string path, object value) =>
document.Add(new JsonPatchOperation { From = null, Operation = Operation.Add, Path = path, Value = value });
private static Dictionary<string, FIBacklogMesa> GetFIBacklogMesaCollection(string json)
{
Dictionary<string, FIBacklogMesa> results = [];
string key;
FIBacklogMesa[]? fiBacklogMesaCollection;
fiBacklogMesaCollection = JsonSerializer.Deserialize(json, FIBacklogMesaCollectionSourceGenerationContext.Default.FIBacklogMesaArray);
if (fiBacklogMesaCollection is null || fiBacklogMesaCollection.Length == 0)
throw new NullReferenceException();
foreach (FIBacklogMesa fiBacklogMesa in fiBacklogMesaCollection)
{
if (string.IsNullOrEmpty(fiBacklogMesa.Req))
continue;
if (string.IsNullOrEmpty(fiBacklogMesa.Submitted))
continue;
if (string.IsNullOrEmpty(fiBacklogMesa.Requestor))
continue;
key = $"{fiBacklogMesa.Req} - ";
if (results.ContainsKey(key))
continue;
results.Add(key, fiBacklogMesa);
}
return results;
}
private static string GetIds(HttpClient httpClient, string basePage, string api, string query)
{
StringBuilder result = new();
Task<HttpResponseMessage> httpResponseMessageTask = httpClient.GetAsync(string.Concat(basePage, api, query));
httpResponseMessageTask.Wait();
if (!httpResponseMessageTask.Result.IsSuccessStatusCode)
throw new Exception(httpResponseMessageTask.Result.StatusCode.ToString());
Task<Stream> streamTask = httpResponseMessageTask.Result.Content.ReadAsStreamAsync();
streamTask.Wait();
if (!streamTask.Result.CanRead)
throw new NullReferenceException(nameof(streamTask));
WIQL.Root? root = JsonSerializer.Deserialize(streamTask.Result, WIQL.WIQLRootSourceGenerationContext.Default.Root);
streamTask.Result.Dispose();
if (root is null || root.WorkItems is null)
throw new NullReferenceException(nameof(root));
foreach (WIQL.WorkItem workItem in root.WorkItems)
_ = result.Append(workItem.Id).Append(',');
if (result.Length > 0)
_ = result.Remove(result.Length - 1, 1);
return result.ToString();
}
private static ReadOnlyCollection<ValueWithReq> GetWorkItems(HttpClient httpClient, string sourceDirectory, string basePage, string api, string ids)
{
List<ValueWithReq> results = [];
int req;
string json;
string file;
Value? value;
string[] segments;
Task<HttpResponseMessage> httpResponseMessageTask = httpClient.GetAsync(string.Concat(basePage, api, $"/workitems?ids={ids}"));
httpResponseMessageTask.Wait();
if (!httpResponseMessageTask.Result.IsSuccessStatusCode)
throw new Exception(httpResponseMessageTask.Result.StatusCode.ToString());
Task<Stream> streamTask = httpResponseMessageTask.Result.Content.ReadAsStreamAsync();
streamTask.Wait();
if (!streamTask.Result.CanRead)
throw new NullReferenceException(nameof(streamTask));
JsonElement? result = JsonSerializer.Deserialize<JsonElement>(streamTask.Result);
if (result is null || result.Value.ValueKind != JsonValueKind.Object)
throw new NullReferenceException(nameof(result));
JsonElement[] jsonElements = result.Value.EnumerateObject().Last().Value.EnumerateArray().ToArray();
foreach (JsonElement jsonElement in jsonElements)
{
json = jsonElement.GetRawText();
value = JsonSerializer.Deserialize(json, ValueSourceGenerationContext.Default.Value);
if (value is null)
continue;
segments = value.Fields.SystemTitle.Split('-');
if (segments.Length < 2)
continue;
if (!int.TryParse(segments[0], out req) || req == 0)
continue;
file = Path.Combine(sourceDirectory, $"{req}.json");
File.WriteAllText(file, json);
results.Add(new(value, req, json));
}
return new(results);
}
private static ReadOnlyCollection<ValueWithReq> RemoveFrom(Dictionary<string, FIBacklogMesa> keyToFIBacklogMesa, ReadOnlyCollection<ValueWithReq> valueWithReqCollection)
{
List<ValueWithReq> results = [];
foreach (ValueWithReq valueWithReq in valueWithReqCollection)
{
if (keyToFIBacklogMesa.Remove($"{valueWithReq.Req} - "))
continue;
results.Add(valueWithReq);
}
return new(results);
}
private static DateTime? GetCommitDate(FIBacklogMesa fiBacklogMesa)
{
DateTime? result;
DateTime dateTime;
DateTime minDateTime = DateTime.MinValue.AddYears(10);
string commitDate = fiBacklogMesa.CommitDate.Split(' ')[0];
if (string.IsNullOrEmpty(commitDate))
result = null;
else
{
if (DateTime.TryParseExact(commitDate, "MM/dd/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime) && dateTime >= minDateTime)
result = dateTime.AddHours(12).ToUniversalTime();
else
{
if (DateTime.TryParseExact(commitDate, "dd-MMM-yy", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime) && dateTime >= minDateTime)
result = dateTime.AddHours(12).ToUniversalTime();
else
{
if (DateTime.TryParse(commitDate, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime) && dateTime >= minDateTime)
result = dateTime.AddHours(12).ToUniversalTime();
else
result = null;
}
}
}
return result;
}
private static int GetPriority(FIBacklogMesa fiBacklogMesa)
{
int result;
if (string.IsNullOrEmpty(fiBacklogMesa.Priority) || !int.TryParse(fiBacklogMesa.Priority[..1], out int priority) || priority == 0 || priority > 3)
result = 4;
else
result = priority;
return result;
}
private static int? GetPrioritySubset(FIBacklogMesa fiBacklogMesa)
{
int? result;
if (string.IsNullOrEmpty(fiBacklogMesa.PrioritySubset) || !int.TryParse(fiBacklogMesa.PrioritySubset[..1], out int prioritySubset) || prioritySubset == 0 || prioritySubset > 3)
result = null;
else
result = prioritySubset;
return result;
}
private static string GetIterationPath(string project, DateTime submittedDateTime) =>
submittedDateTime.Year != 2024 ? project : string.Concat(project, "\\", submittedDateTime.Year);
private static string GetTitle(FIBacklogMesa fiBacklogMesa)
{
string result = $"{fiBacklogMesa.Req} - {fiBacklogMesa.Subject.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)[0]}";
if (result.Length > 128)
result = result[..127];
return result;
}
private static string GetTitle(FIBacklogMesa fiBacklogMesa, ValueWithReq valueWithReq)
{
string result = $"{valueWithReq.Req} - {fiBacklogMesa.Subject.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)[0]}";
if (result.Length > 128)
result = result[..127];
return result;
}
private static string? GetMappedState(FIBacklogMesa fiBacklogMesa) =>
fiBacklogMesa.Status == "CMP" ? "Closed" : fiBacklogMesa.Status == "UAT" ? "Resolved" : fiBacklogMesa.Status == "In process" ? "Active" : null;
private static JsonPatchDocument GetBugDocument(string project, string site, ReadOnlyDictionary<string, string> assignedToNameToUser, ReadOnlyDictionary<string, string> requestorNameToUser, Task<WorkItem>? uatWorkItemTask, FIBacklogMesa fiBacklogMesa, DateTime submittedDateTime)
{
JsonPatchDocument result = [];
string title = GetTitle(fiBacklogMesa);
string iterationPath = GetIterationPath(project, submittedDateTime);
if (uatWorkItemTask?.Result.Id is not null)
AddPatch(result, "/relations/-", new WorkItemRelation() { Rel = "System.LinkTypes.Hierarchy-Forward", Url = uatWorkItemTask.Result.Url });
AddPatch(result, "/fields/System.AreaPath", string.Concat(project, "\\", site));
AddPatch(result, "/fields/System.IterationPath", iterationPath);
AddPatch(result, "/fields/System.Title", title);
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.Subject}<br>&nbsp;<br>{fiBacklogMesa.Definition}");
if (assignedToNameToUser.TryGetValue(fiBacklogMesa.AssignedTo, out string? assignedToUser))
AddPatch(result, "/fields/System.AssignedTo", assignedToUser);
if (requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser))
AddPatch(result, "/fields/Custom.Requester", requestorUser);
return result;
}
private static JsonPatchDocument GetFeatureDocument(string project, string site, ReadOnlyDictionary<string, string> requestorNameToUser, ReadOnlyDictionary<string, string> assignedToNameToUser, List<string> tags, Task<WorkItem>? userStoryWorkItemTask, FIBacklogMesa fiBacklogMesa, DateTime submittedDateTime)
{
JsonPatchDocument result = [];
string title = GetTitle(fiBacklogMesa);
int priority = GetPriority(fiBacklogMesa);
string? state = GetMappedState(fiBacklogMesa);
int? prioritySubset = GetPrioritySubset(fiBacklogMesa);
if (prioritySubset is not null)
AddPatch(result, "/fields/Microsoft.VSTS.Common.TimeCriticality", prioritySubset);
string iterationPath = GetIterationPath(project, submittedDateTime);
if (userStoryWorkItemTask?.Result.Id is not null)
AddPatch(result, "/relations/-", new WorkItemRelation() { Rel = "System.LinkTypes.Hierarchy-Forward", Url = userStoryWorkItemTask.Result.Url });
AddPatch(result, "/fields/System.AreaPath", string.Concat(project, "\\", site));
if (tags.Count > 0)
{
AddPatch(result, "/fields/System.Tags", tags.Last());
tags.RemoveAt(tags.Count - 1);
}
AddPatch(result, "/fields/System.IterationPath", iterationPath);
AddPatch(result, "/fields/Microsoft.VSTS.Common.Priority", priority);
if (!string.IsNullOrEmpty(fiBacklogMesa.Definition))
AddPatch(result, "/fields/System.Description", $"{fiBacklogMesa.Subject}<br>&nbsp;<br>{fiBacklogMesa.Definition}");
if (!string.IsNullOrEmpty(state))
AddPatch(result, "/fields/System.State", state);
if (!string.IsNullOrEmpty(fiBacklogMesa.EstEffortDays) && int.TryParse(fiBacklogMesa.EstEffortDays, out int estEffortDays) && estEffortDays != 0)
AddPatch(result, "/fields/Microsoft.VSTS.Scheduling.Effort", estEffortDays);
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", title);
if (assignedToNameToUser.TryGetValue(fiBacklogMesa.AssignedTo, out string? assignedToUser))
AddPatch(result, "/fields/System.AssignedTo", assignedToUser);
if (requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser))
AddPatch(result, "/fields/Custom.Requester", requestorUser);
// https://tfs.intra.infineon.com/tfs/ManufacturingIT/Mesa_FI/_apis/wit/workitemtypes/feature/fields?api-version=7.0
return result;
}
private static List<string> GetTags(FIBacklogMesa fiBacklogMesa)
{
List<string> results = [];
foreach (string tag in fiBacklogMesa.SystemS.Split('/'))
{
if (string.IsNullOrEmpty(tag.Trim()))
continue;
results.Add(tag.Trim());
}
return results;
}
private static List<string> GetTags(Fields fields)
{
List<string> results = [];
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 CreateWorkItem(WorkItemTrackingHttpClient workItemTrackingHttpClient, string project, string site, ReadOnlyDictionary<string, string> assignedToNameToUser, ReadOnlyDictionary<string, string> requestorNameToUser, FIBacklogMesa fiBacklogMesa)
{
DateTime submittedDateTime;
JsonPatchDocument tagDocument;
Task<WorkItem>? workItem = null;
List<string> tags = GetTags(fiBacklogMesa);
bool isBugFix = fiBacklogMesa.Priority == "0 - BugFix";
if (assignedToNameToUser.Count > requestorNameToUser.Count)
throw new Exception();
if (!DateTime.TryParse(fiBacklogMesa.Submitted, out submittedDateTime))
submittedDateTime = DateTime.MinValue;
if (isBugFix)
{
Task<WorkItem>? uatWorkItemTask = null;
JsonPatchDocument bugDocument = GetBugDocument(project, site, requestorNameToUser, assignedToNameToUser, uatWorkItemTask, fiBacklogMesa, submittedDateTime);
workItem = workItemTrackingHttpClient.CreateWorkItemAsync(bugDocument, project, "Bug");
workItem.Wait();
}
if (!isBugFix)
{
Task<WorkItem>? userStoryWorkItemTask = null;
JsonPatchDocument featureDocument = GetFeatureDocument(project, site, requestorNameToUser, assignedToNameToUser, tags, userStoryWorkItemTask, fiBacklogMesa, submittedDateTime);
workItem = workItemTrackingHttpClient.CreateWorkItemAsync(featureDocument, project, "Feature");
workItem.Wait();
}
for (int i = tags.Count - 1; i > -1; i--)
{
if (workItem is null)
continue;
if (workItem.Result.Id is null)
throw new NotSupportedException();
tagDocument = [];
AddPatch(tagDocument, "/fields/System.Tags", tags[i]);
tags.RemoveAt(i);
workItem = workItemTrackingHttpClient.UpdateWorkItemAsync(tagDocument, workItem.Result.Id.Value);
workItem.Wait();
}
}
private static void KillTime(int loops)
{
for (int i = 1; i < loops; i++)
Thread.Sleep(500);
}
private static void Update(HttpClient httpClient, string basePage, string api, string query, HttpContent httpContent)
{
#if Windows
Task<HttpResponseMessage> httpResponseMessageTask = httpClient.PatchAsync(string.Concat(basePage, api, query), httpContent);
httpResponseMessageTask.Wait();
if (!httpResponseMessageTask.Result.IsSuccessStatusCode)
throw new Exception(httpResponseMessageTask.Result.StatusCode.ToString());
Task<string> stringTask = httpResponseMessageTask.Result.Content.ReadAsStringAsync();
stringTask.Wait();
#endif
KillTime(30);
}
private static void Update(HttpClient httpClient, string basePage, string api, WorkItemTrackingHttpClient workItemTrackingHttpClient, string sync, ValueWithReq valueWithReq)
{
JsonPatchDocument result = [];
AddPatch(result, "/fields/System.Tags", sync);
Task<WorkItem> workItem = workItemTrackingHttpClient.UpdateWorkItemAsync(result, valueWithReq.Value.Id);
workItem.Wait();
if (result is null)
{
var payload = new
{
op = "replace",
path = "/fields/System.IterationPath",
value = "Mesa_FI"
};
string stringPayload = JsonSerializer.Serialize(payload);
HttpContent httpContent = new StringContent($"[{stringPayload}]", Encoding.UTF8, "application/json-patch+json");
Update(httpClient, basePage, api, $"/workitems/{valueWithReq.Value.Id}?api-version=1.0", httpContent);
}
}
private static int SetSyncTag(HttpClient httpClient,
string basePage,
string api,
WorkItemTrackingHttpClient workItemTrackingHttpClient,
ReadOnlyDictionary<string, string> assignedToNameToUser,
ReadOnlyDictionary<string, string> requestorNameToUser,
Dictionary<string, FIBacklogMesa> keyToFIBacklogMesa,
ReadOnlyCollection<ValueWithReq> valueWithReqCollection)
{
int result = 0;
string key;
string title;
int priority;
bool isBugFix;
string? state;
List<string> tags;
TimeSpan timeSpan;
DateTime? dateTime;
List<string> 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);
title = GetTitle(fiBacklogMesa, valueWithReq);
isBugFix = fiBacklogMesa.Priority == "0 - BugFix";
_ = requestorNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? requestorUser);
if (!string.IsNullOrEmpty(requestorUser) && (valueWithReq.Value.Fields.CustomRequester is null || !valueWithReq.Value.Fields.CustomRequester.UniqueName.Equals(requestorUser, StringComparison.CurrentCultureIgnoreCase)))
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
_ = assignedToNameToUser.TryGetValue(fiBacklogMesa.Requestor, out string? assignedToUser);
if (!string.IsNullOrEmpty(assignedToUser) && (valueWithReq.Value.Fields.SystemAssignedTo is null || !valueWithReq.Value.Fields.SystemAssignedTo.UniqueName.Equals(assignedToUser, StringComparison.CurrentCultureIgnoreCase)))
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
if (valueWithReq.Value.Fields.SystemTitle != title)
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
foreach (string tag in tags)
{
if (compareTags.Contains(tag))
continue;
_ = tags.Remove(tag);
break;
}
if (tags.Count != compareTags.Count)
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
if ((isBugFix && valueWithReq.Value.Fields.SystemWorkItemType != "Bug") || (!isBugFix && valueWithReq.Value.Fields.SystemWorkItemType == "Bug"))
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
if (!isBugFix)
{
priority = GetPriority(fiBacklogMesa);
if (valueWithReq.Value.Fields.MicrosoftVSTSCommonPriority != priority)
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
}
state = GetMappedState(fiBacklogMesa);
if (!string.IsNullOrEmpty(state) && valueWithReq.Value.Fields.SystemState != state)
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
if (!isBugFix && int.TryParse(fiBacklogMesa.EstEffortDays, out int estEffortDays) && valueWithReq.Value.Fields.Effort != estEffortDays)
{
result += 1;
Update(httpClient, basePage, api, 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)
{
result += 1;
Update(httpClient, basePage, api, workItemTrackingHttpClient, sync, valueWithReq);
continue;
}
}
}
return result;
}
private static void CreateWorkItems(HttpClient httpClient,
string sourceDirectory,
string basePage,
string api,
string query,
WorkItemTrackingHttpClient workItemTrackingHttpClient,
string project,
string site,
ReadOnlyDictionary<string, string> assignedToNameToUser,
ReadOnlyDictionary<string, string> requestorNameToUser,
string json)
{
int counter = 0;
string ids = GetIds(httpClient, basePage, api, query);
Dictionary<string, FIBacklogMesa> keyToFIBacklogMesa = GetFIBacklogMesaCollection(json);
ReadOnlyCollection<ValueWithReq> valueWithReqCollection = string.IsNullOrEmpty(ids) ? new([]) : GetWorkItems(httpClient, sourceDirectory, basePage, api, ids);
int updated = SetSyncTag(httpClient, basePage, api, workItemTrackingHttpClient, assignedToNameToUser, requestorNameToUser, keyToFIBacklogMesa, valueWithReqCollection);
if (updated == 0)
{
ReadOnlyCollection<ValueWithReq> extra = RemoveFrom(keyToFIBacklogMesa, valueWithReqCollection);
foreach (KeyValuePair<string, FIBacklogMesa> keyValuePair in keyToFIBacklogMesa)
{
if (keyToFIBacklogMesa.Count == extra.Count)
break;
CreateWorkItem(workItemTrackingHttpClient, project, site, assignedToNameToUser, requestorNameToUser, keyValuePair.Value);
counter++;
}
}
}
private static void CreateWorkItems(ILogger<Worker> logger, string sourceDirectory, string api, string site, string query, string project, string basePage, string baseAddress, byte[] bytes, string[] assignedToNames, string[] requestorNames, string reportFullPath, MediaTypeWithQualityHeaderValue mediaTypeWithQualityHeaderValue, WorkItemTrackingHttpClient workItemTrackingHttpClient, HttpClient httpClient)
{
string base64 = Convert.ToBase64String(bytes);
string json = File.ReadAllText(reportFullPath);
httpClient.DefaultRequestHeaders.Authorization = new("Basic", base64);
httpClient.DefaultRequestHeaders.Accept.Add(mediaTypeWithQualityHeaderValue);
ReadOnlyDictionary<string, string> requestorNameToUser = GetRequestorNameToUser(requestorNames);
ReadOnlyDictionary<string, string> assignedToNameToUser = GetAssignedToNameToUser(assignedToNames);
logger.LogInformation("{baseAddress}{basePage}/{project}{api}{query}", baseAddress, basePage, HttpUtility.HtmlEncode(project), api, query);
CreateWorkItems(httpClient, sourceDirectory, basePage, api, query, workItemTrackingHttpClient, project, site, new(assignedToNameToUser), new(requestorNameToUser), json);
}
private static ReadOnlyDictionary<string, string> GetAssignedToNameToUser(string[] assignedToNames)
{
Dictionary<string, string> results = [];
string[] segments;
foreach (string assignedToName in assignedToNames)
{
segments = assignedToName.Split('|');
if (segments.Length != 2)
continue;
results.Add(segments[0], segments[1]);
}
return new(results);
}
private static ReadOnlyDictionary<string, string> GetRequestorNameToUser(string[] requestorNames)
{
Dictionary<string, string> results = [];
string[] segments;
foreach (string requestorName in requestorNames)
{
segments = requestorName.Split('|');
if (segments.Length != 2)
continue;
results.Add(segments[0], segments[1]);
}
return new(results);
}
internal static void CreateWorkItems(ILogger<Worker> logger, List<string> args)
{
string api = args[6];
string pat = args[8];
string site = args[2];
string query = args[7];
string project = args[5];
string basePage = args[4];
string baseAddress = args[3];
string sourceDirectory = args[0];
VssBasicCredential credential = new("", pat);
string[] requestorNames = args[11].Split(',');
string[] assignedToNames = args[10].Split(',');
byte[] bytes = Encoding.ASCII.GetBytes($":{pat}");
string reportFullPath = Path.GetFullPath(Path.Combine(sourceDirectory, args[9]));
VssConnection connection = new(new(string.Concat(baseAddress, basePage)), credential);
MediaTypeWithQualityHeaderValue mediaTypeWithQualityHeaderValue = new("application/json");
WorkItemTrackingHttpClient workItemTrackingHttpClient = connection.GetClient<WorkItemTrackingHttpClient>();
HttpClient httpClient = new(new HttpClientHandler() { UseDefaultCredentials = true }) { BaseAddress = new(baseAddress) };
CreateWorkItems(logger, sourceDirectory, api, site, query, project, basePage, baseAddress, bytes, assignedToNames, requestorNames, reportFullPath, mediaTypeWithQualityHeaderValue, workItemTrackingHttpClient, httpClient);
}
}

22
Day/Q32024/WIQL/Column.cs Normal file
View File

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WIQL;
public class Column
{
[JsonConstructor]
public Column(
string referenceName,
string name,
string url
)
{
ReferenceName = referenceName;
Name = name;
Url = url;
}
public string ReferenceName { get; set; } // { init; get; }
public string Name { get; set; } // { init; get; }
public string Url { get; set; } // { init; get; }
}

22
Day/Q32024/WIQL/Field.cs Normal file
View File

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WIQL;
public class Field
{
[JsonConstructor]
public Field(
string referenceName,
string name,
string url
)
{
ReferenceName = referenceName;
Name = name;
Url = url;
}
public string ReferenceName { get; set; } // { init; get; }
public string Name { get; set; } // { init; get; }
public string Url { get; set; } // { init; get; }
}

37
Day/Q32024/WIQL/Root.cs Normal file
View File

@ -0,0 +1,37 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WIQL;
public class Root
{
[JsonConstructor]
public Root(
string queryType,
string queryResultType,
DateTime asOf,
Column[] columns,
SortColumn[] sortColumns,
WorkItem[] workItems
)
{
QueryType = queryType;
QueryResultType = queryResultType;
AsOf = asOf;
Columns = columns;
SortColumns = sortColumns;
WorkItems = workItems;
}
public string QueryType { get; set; } // { init; get; }
public string QueryResultType { get; set; } // { init; get; }
public DateTime AsOf { get; set; } // { init; get; }
public Column[] Columns { get; set; } // { init; get; }
public SortColumn[] SortColumns { get; set; } // { init; get; }
public WorkItem[] WorkItems { get; set; } // { init; get; }
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(Root))]
internal partial class WIQLRootSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WIQL;
public class SortColumn
{
[JsonConstructor]
public SortColumn(
Field field,
bool descending
)
{
Field = field;
Descending = descending;
}
public Field Field { get; set; } // { init; get; }
public bool Descending { get; set; } // { init; get; }
}

View File

@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WIQL;
public class WorkItem
{
[JsonConstructor]
public WorkItem(
int id,
string url
)
{
Id = id;
Url = url;
}
public int Id { get; set; } // { init; get; }
public string Url { get; set; } // { init; get; }
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class Avatar
{
[JsonConstructor]
public Avatar(
string href
) => Href = href;
public string Href { get; } // { init; get; }
}

View File

@ -0,0 +1,27 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class CommentVersionRef
{
[JsonConstructor]
public CommentVersionRef(
int commentId,
int version,
string url
)
{
CommentId = commentId;
Version = version;
URL = url;
}
[JsonPropertyName("commentId")]
public int CommentId { get; } // { init; get; }
[JsonPropertyName("version")]
public int Version { get; } // { init; get; }
[JsonPropertyName("url")]
public string URL { get; } // { init; get; }
}

View File

@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class CustomRequester
{
[JsonConstructor]
public CustomRequester(
string displayName,
string url,
Links links,
string id,
string uniqueName,
string imageUrl,
string descriptor
)
{
DisplayName = displayName;
Url = url;
Links = links;
Id = id;
UniqueName = uniqueName;
ImageUrl = imageUrl;
Descriptor = descriptor;
}
[JsonPropertyName("displayName")]
public string DisplayName { get; }
[JsonPropertyName("url")]
public string Url { get; }
[JsonPropertyName("_links")]
public Links Links { get; }
[JsonPropertyName("id")]
public string Id { get; }
[JsonPropertyName("uniqueName")]
public string UniqueName { get; }
[JsonPropertyName("imageUrl")]
public string ImageUrl { get; }
[JsonPropertyName("descriptor")]
public string Descriptor { get; }
}

View File

@ -0,0 +1,117 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class Fields
{
[JsonConstructor]
public Fields(
string systemAreaPath,
string systemTeamProject,
string systemIterationPath,
string systemWorkItemType,
string systemState,
string systemReason,
CustomRequester customRequester,
SystemAssignedTo systemAssignedTo,
DateTime systemCreatedDate,
SystemCreatedBy systemCreatedBy,
DateTime systemChangedDate,
SystemChangedBy systemChangedBy,
int systemCommentCount,
string systemTitle,
DateTime microsoftVSTSCommonStateChangeDate,
int microsoftVSTSCommonPriority,
string systemDescription,
string systemTags,
string systemHistory,
float? effort,
DateTime targetDate
)
{
SystemAreaPath = systemAreaPath;
SystemTeamProject = systemTeamProject;
SystemIterationPath = systemIterationPath;
SystemWorkItemType = systemWorkItemType;
SystemState = systemState;
SystemReason = systemReason;
CustomRequester = customRequester;
SystemAssignedTo = systemAssignedTo;
SystemCreatedDate = systemCreatedDate;
SystemCreatedBy = systemCreatedBy;
SystemChangedDate = systemChangedDate;
SystemChangedBy = systemChangedBy;
SystemCommentCount = systemCommentCount;
SystemTitle = systemTitle;
MicrosoftVSTSCommonStateChangeDate = microsoftVSTSCommonStateChangeDate;
MicrosoftVSTSCommonPriority = microsoftVSTSCommonPriority;
SystemDescription = systemDescription;
SystemTags = systemTags;
SystemHistory = systemHistory;
Effort = effort;
TargetDate = targetDate;
}
[JsonPropertyName("System.AreaPath")]
public string SystemAreaPath { get; } // { init; get; }
[JsonPropertyName("System.TeamProject")]
public string SystemTeamProject { get; } // { init; get; }
[JsonPropertyName("System.IterationPath")]
public string SystemIterationPath { get; } // { init; get; }
[JsonPropertyName("System.WorkItemType")]
public string SystemWorkItemType { get; } // { init; get; }
[JsonPropertyName("System.State")]
public string SystemState { get; } // { init; get; }
[JsonPropertyName("System.Reason")]
public string SystemReason { get; } // { init; get; }
[JsonPropertyName("Custom.Requester")]
public CustomRequester CustomRequester { get; } // { init; get; }
[JsonPropertyName("System.AssignedTo")]
public SystemAssignedTo SystemAssignedTo { get; } // { init; get; }
[JsonPropertyName("System.CreatedDate")]
public DateTime SystemCreatedDate { get; } // { init; get; }
[JsonPropertyName("System.CreatedBy")]
public SystemCreatedBy SystemCreatedBy { get; } // { init; get; }
[JsonPropertyName("System.ChangedDate")]
public DateTime SystemChangedDate { get; } // { init; get; }
[JsonPropertyName("System.ChangedBy")]
public SystemChangedBy SystemChangedBy { get; } // { init; get; }
[JsonPropertyName("System.CommentCount")]
public int SystemCommentCount { get; } // { init; get; }
[JsonPropertyName("System.Title")]
public string SystemTitle { get; } // { init; get; }
[JsonPropertyName("Microsoft.VSTS.Common.StateChangeDate")]
public DateTime MicrosoftVSTSCommonStateChangeDate { get; } // { init; get; }
[JsonPropertyName("Microsoft.VSTS.Common.Priority")]
public int MicrosoftVSTSCommonPriority { get; } // { init; get; }
[JsonPropertyName("System.Description")]
public string SystemDescription { get; } // { init; get; }
[JsonPropertyName("System.Tags")]
public string SystemTags { get; } // { init; get; }
[JsonPropertyName("System.History")]
public string SystemHistory { 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; }
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class Html
{
[JsonConstructor]
public Html(
string href
) => Href = href;
public string Href { get; } // { init; get; }
}

View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class Links
{
[JsonConstructor]
public Links(
Avatar avatar
) => Avatar = avatar;
[JsonPropertyName("avatar")]
public Avatar Avatar { get; }
}

View File

@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class SystemAssignedTo
{
[JsonConstructor]
public SystemAssignedTo(
string displayName,
string url,
Links links,
string id,
string uniqueName,
string imageUrl,
string descriptor
)
{
DisplayName = displayName;
Url = url;
Links = links;
Id = id;
UniqueName = uniqueName;
ImageUrl = imageUrl;
Descriptor = descriptor;
}
[JsonPropertyName("displayName")]
public string DisplayName { get; }
[JsonPropertyName("url")]
public string Url { get; }
[JsonPropertyName("_links")]
public Links Links { get; }
[JsonPropertyName("id")]
public string Id { get; }
[JsonPropertyName("uniqueName")]
public string UniqueName { get; }
[JsonPropertyName("imageUrl")]
public string ImageUrl { get; }
[JsonPropertyName("descriptor")]
public string Descriptor { get; }
}

View File

@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class SystemChangedBy
{
[JsonConstructor]
public SystemChangedBy(
string displayName,
string url,
Links links,
string id,
string uniqueName,
string imageUrl,
string descriptor
)
{
DisplayName = displayName;
Url = url;
Links = links;
Id = id;
UniqueName = uniqueName;
ImageUrl = imageUrl;
Descriptor = descriptor;
}
[JsonPropertyName("displayName")]
public string DisplayName { get; }
[JsonPropertyName("url")]
public string Url { get; }
[JsonPropertyName("_links")]
public Links Links { get; }
[JsonPropertyName("id")]
public string Id { get; }
[JsonPropertyName("uniqueName")]
public string UniqueName { get; }
[JsonPropertyName("imageUrl")]
public string ImageUrl { get; }
[JsonPropertyName("descriptor")]
public string Descriptor { get; }
}

View File

@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class SystemCreatedBy
{
[JsonConstructor]
public SystemCreatedBy(
string displayName,
string url,
Links links,
string id,
string uniqueName,
string imageUrl,
string descriptor
)
{
DisplayName = displayName;
Url = url;
Links = links;
Id = id;
UniqueName = uniqueName;
ImageUrl = imageUrl;
Descriptor = descriptor;
}
[JsonPropertyName("displayName")]
public string DisplayName { get; }
[JsonPropertyName("url")]
public string Url { get; }
[JsonPropertyName("_links")]
public Links Links { get; }
[JsonPropertyName("id")]
public string Id { get; }
[JsonPropertyName("uniqueName")]
public string UniqueName { get; }
[JsonPropertyName("imageUrl")]
public string ImageUrl { get; }
[JsonPropertyName("descriptor")]
public string Descriptor { get; }
}

View File

@ -0,0 +1,49 @@
using System.Text.Json.Serialization;
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class Value
{
[JsonConstructor]
public Value(
int id,
int rev,
Fields fields,
CommentVersionRef commentVersionRef,
string url
)
{
Id = id;
Rev = rev;
Fields = fields;
CommentVersionRef = commentVersionRef;
Url = url;
}
[JsonPropertyName("id")]
public int Id { get; }
[JsonPropertyName("rev")]
public int Rev { get; }
[JsonPropertyName("fields")]
public Fields Fields { get; }
[JsonPropertyName("commentVersionRef")]
public CommentVersionRef CommentVersionRef { get; }
[JsonPropertyName("url")]
public string Url { get; }
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(Value[]))]
internal partial class ValueCollectionSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true)]
[JsonSerializable(typeof(Value))]
internal partial class ValueSourceGenerationContext : JsonSerializerContext
{
}

View File

@ -0,0 +1,19 @@
namespace File_Folder_Helper.Day.Q32024.WorkItems;
public class ValueWithReq
{
public ValueWithReq(
Value value,
int req,
string json
)
{
Value = value;
Req = req;
Json = json;
}
public Value Value { get; set; } // { init; get; }
public int Req { get; set; } // { init; get; }
public string Json { get; set; } // { init; get; }
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
@ -16,6 +16,7 @@
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="16.205.1" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.7" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
<PackageReference Include="TextCopy" Version="6.2.1" />