ExtractKanban
This commit is contained in:
parent
930963965d
commit
618fa0d55f
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -29,6 +29,7 @@
|
||||
"Kofax",
|
||||
"Linc",
|
||||
"mesfs",
|
||||
"mestsa",
|
||||
"NpgSql",
|
||||
"NSFX",
|
||||
"OBJE",
|
||||
|
@ -1,13 +1,17 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
#if WorkItems
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
#endif
|
||||
|
||||
namespace File_Folder_Helper.ADO2024.PI4;
|
||||
|
||||
internal static partial class Helper20241108
|
||||
{
|
||||
|
||||
#if WorkItems
|
||||
|
||||
private record Attribute([property: JsonPropertyName("isLocked")] bool IsLocked,
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("parameterTitle")] string? ParameterTitle,
|
||||
@ -367,4 +371,14 @@ internal static partial class Helper20241108
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
internal static void WriteMarkdown(ILogger<Worker> logger, List<string> args)
|
||||
{
|
||||
logger.LogError("WriteMarkdown is not available in WorkItems {args[0]}", args[0]);
|
||||
logger.LogError("WriteMarkdown is not available in WorkItems {args[1]}", args[1]);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
305
ADO2025/PI4/Helper-2025-02-04.cs
Normal file
305
ADO2025/PI4/Helper-2025-02-04.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace File_Folder_Helper.ADO2025.PI4;
|
||||
|
||||
internal static partial class Helper20250204
|
||||
{
|
||||
|
||||
[GeneratedRegex("([A-Z]+(.))")]
|
||||
private static partial Regex UpperCase();
|
||||
|
||||
[GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
|
||||
private static partial Regex InvalidCharacter();
|
||||
|
||||
private record H1ParamCaseAndState(string H1, string ParamCase, string State)
|
||||
{
|
||||
|
||||
private static string GetParamCase(string value)
|
||||
{
|
||||
string result;
|
||||
StringBuilder stringBuilder = new(value);
|
||||
Match[] matches = UpperCase().Matches(value).ToArray();
|
||||
for (int i = matches.Length - 1; i > -1; i--)
|
||||
_ = stringBuilder.Insert(matches[i].Index, '-');
|
||||
string[] segments = InvalidCharacter().Split(stringBuilder.ToString().ToLower());
|
||||
result = string.Join('-', segments).Trim('-');
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetState(string value) =>
|
||||
value switch
|
||||
{
|
||||
"New" => "ToDo",
|
||||
"Active" => "In Progress",
|
||||
"Closed" => "Done",
|
||||
_ => "Backlog",
|
||||
};
|
||||
|
||||
internal static H1ParamCaseAndState Get(WorkItem workItem)
|
||||
{
|
||||
H1ParamCaseAndState result;
|
||||
string paramCase = GetParamCase(workItem.Title);
|
||||
string state = GetState(workItem.State);
|
||||
result = new(workItem.Title, paramCase, state);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record Attribute([property: JsonPropertyName("isLocked")] bool IsLocked,
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("parameterTitle")] string? ParameterTitle,
|
||||
[property: JsonPropertyName("state")] string? State,
|
||||
[property: JsonPropertyName("workItemType")] string? WorkItemType);
|
||||
|
||||
private record Relation([property: JsonPropertyName("attributes")] Attribute Attributes,
|
||||
[property: JsonPropertyName("id")] int Id,
|
||||
[property: JsonPropertyName("rel")] string Rel);
|
||||
|
||||
private record WorkItem(DateTime? ActivatedDate,
|
||||
string AreaPath,
|
||||
string? AssignedTo,
|
||||
long? BusinessValue,
|
||||
DateTime ChangedDate,
|
||||
DateTime? ClosedDate,
|
||||
int CommentCount,
|
||||
DateTime CreatedDate,
|
||||
string Description,
|
||||
long? Effort,
|
||||
int Id,
|
||||
string IterationPath,
|
||||
int? Parent,
|
||||
int? Priority,
|
||||
Relation[]? Relations,
|
||||
string? Requester,
|
||||
DateTime? ResolvedDate,
|
||||
int Revision,
|
||||
long? RiskReductionMinusOpportunityEnablement,
|
||||
DateTime? StartDate,
|
||||
string State,
|
||||
string Tags,
|
||||
DateTime? TargetDate,
|
||||
long? TimeCriticality,
|
||||
string Title,
|
||||
string? Violation,
|
||||
long? WeightedShortestJobFirst,
|
||||
string WorkItemType)
|
||||
{
|
||||
|
||||
public override string ToString() => $"{Id} - {WorkItemType} - {Title}";
|
||||
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(WorkItem))]
|
||||
private partial class WorkItemSourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(WorkItem[]))]
|
||||
private partial class WorkItemCollectionSourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
private static string GetTaskText(string directory, string rootDirectory) =>
|
||||
string.Join(Environment.NewLine,
|
||||
[
|
||||
"{",
|
||||
"\"version\": \"2.0.0\",",
|
||||
"\"tasks\": [",
|
||||
"{",
|
||||
"\"label\": \"File-Folder-Helper AOT s X Day-Helper-2025-02-04\",",
|
||||
"\"type\": \"shell\",",
|
||||
"\"command\": \"L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe\",",
|
||||
"\"args\": [",
|
||||
"\"s\",",
|
||||
"\"X\",",
|
||||
$"\"{directory}\",",
|
||||
"\"Day-Helper-2025-02-04\",",
|
||||
$"\"{rootDirectory}\",",
|
||||
"],",
|
||||
"\"problemMatcher\": []",
|
||||
"},",
|
||||
"{",
|
||||
"\"label\": \"File-Folder-Helper AOT s X Day-Helper-2024-06-23\",",
|
||||
"\"type\": \"shell\",",
|
||||
"\"command\": \"L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe\",",
|
||||
"\"args\": [",
|
||||
"\"s\",",
|
||||
"\"X\",",
|
||||
$"\"{directory}\",",
|
||||
"\"Day-Helper-2024-06-23\",",
|
||||
"\"*.md\",",
|
||||
"\"##_Sub-tasks\",",
|
||||
"\"-_[code-insiders](\",",
|
||||
"\"index.md\",",
|
||||
"\"-_[,](\",",
|
||||
"\"##_Done\",",
|
||||
"\".kan\",",
|
||||
$"\"{rootDirectory}\",",
|
||||
"\"316940400000\",",
|
||||
"],",
|
||||
"\"problemMatcher\": []",
|
||||
"}",
|
||||
"]",
|
||||
"}",
|
||||
]);
|
||||
|
||||
private static void WriteTaskFile(string sourceDirectory, string rootDirectory)
|
||||
{
|
||||
string tasksFile = Path.Combine(sourceDirectory, ".vscode", "tasks.json");
|
||||
string oldText = File.ReadAllText(tasksFile);
|
||||
string jsonSafeDirectory = sourceDirectory.Replace('\\', '/');
|
||||
if (!oldText.Contains(jsonSafeDirectory))
|
||||
{
|
||||
string text = GetTaskText(jsonSafeDirectory, rootDirectory);
|
||||
File.WriteAllText(tasksFile, text);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFilter(ReadOnlyCollection<H1ParamCaseAndState> collection, string filter) =>
|
||||
string.Join(Environment.NewLine, from l in collection where l.State == filter select $"- [{l.ParamCase}](tasks/{l.ParamCase}.md)");
|
||||
|
||||
private static string GetIndexText(WorkItem workItem, H1ParamCaseAndState h1ParamCaseAndState, ReadOnlyCollection<H1ParamCaseAndState> collection) =>
|
||||
string.Join(Environment.NewLine, [
|
||||
"---",
|
||||
"startedColumns:",
|
||||
" - 'In Progress'",
|
||||
"completedColumns:",
|
||||
" - Done",
|
||||
"---",
|
||||
string.Empty,
|
||||
$"# {workItem.Id} - {h1ParamCaseAndState.H1}",
|
||||
string.Empty,
|
||||
"## Backlog",
|
||||
string.Empty,
|
||||
GetFilter(collection, "Backlog"),
|
||||
string.Empty,
|
||||
"## Todo",
|
||||
string.Empty,
|
||||
GetFilter(collection, "ToDo"),
|
||||
string.Empty,
|
||||
"## In Progress",
|
||||
string.Empty,
|
||||
GetFilter(collection, "In Progress"),
|
||||
string.Empty,
|
||||
"## Done",
|
||||
string.Empty,
|
||||
GetFilter(collection, "Done"),
|
||||
string.Empty
|
||||
]);
|
||||
|
||||
private static string GetIndexMarkdown(FileInfo fileInfo, ReadOnlyCollection<WorkItem> workItems)
|
||||
{
|
||||
string result;
|
||||
H1ParamCaseAndState h1ParamCaseAndState;
|
||||
List<H1ParamCaseAndState> collection = [];
|
||||
foreach (WorkItem w in workItems)
|
||||
{
|
||||
h1ParamCaseAndState = H1ParamCaseAndState.Get(w);
|
||||
collection.Add(h1ParamCaseAndState);
|
||||
}
|
||||
string line = Environment.NewLine;
|
||||
string json = File.ReadAllText(fileInfo.FullName);
|
||||
WorkItem? workItem = JsonSerializer.Deserialize(json, WorkItemSourceGenerationContext.Default.WorkItem) ??
|
||||
throw new NullReferenceException(nameof(WorkItem));
|
||||
h1ParamCaseAndState = H1ParamCaseAndState.Get(workItem);
|
||||
string text = GetIndexText(workItem, h1ParamCaseAndState, collection.AsReadOnly());
|
||||
result = text.Replace($"{line}{line}{line}{line}", $"{line}{line}").Replace("408m](tasks", "408M](tasks");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<WorkItem> GetWorkItems(string[] files)
|
||||
{
|
||||
List<WorkItem> results = [];
|
||||
string json;
|
||||
WorkItem? workItem;
|
||||
foreach (string file in files)
|
||||
{
|
||||
json = File.ReadAllText(file);
|
||||
workItem = JsonSerializer.Deserialize(json, WorkItemSourceGenerationContext.Default.WorkItem);
|
||||
if (workItem is null)
|
||||
continue;
|
||||
results.Add(workItem);
|
||||
}
|
||||
return results.AsReadOnly();
|
||||
}
|
||||
|
||||
private static void ExtractKanban(string searchPattern, string rootDirectory, DirectoryInfo kanbanDirectory, FileInfo fileInfo)
|
||||
{
|
||||
string checkFile;
|
||||
string weekOfYear;
|
||||
string workItemDirectory;
|
||||
string line = Environment.NewLine;
|
||||
H1ParamCaseAndState h1ParamCaseAndState;
|
||||
Calendar calendar = new CultureInfo("en-US").Calendar;
|
||||
string tasksDirectory = Path.Combine(kanbanDirectory.FullName, "tasks");
|
||||
if (!Directory.Exists(tasksDirectory))
|
||||
_ = Directory.CreateDirectory(tasksDirectory);
|
||||
string[] files = Directory.GetFiles(tasksDirectory, searchPattern, SearchOption.TopDirectoryOnly);
|
||||
ReadOnlyCollection<WorkItem> workItems = GetWorkItems(files);
|
||||
string markdown = GetIndexMarkdown(fileInfo, workItems);
|
||||
string indexFile = Path.Combine(kanbanDirectory.FullName, "index.md");
|
||||
string markdownOld = File.Exists(indexFile) ? File.ReadAllText(indexFile) : string.Empty;
|
||||
if (markdown != markdownOld)
|
||||
File.WriteAllText(indexFile, markdown);
|
||||
foreach (WorkItem workItem in workItems)
|
||||
{
|
||||
h1ParamCaseAndState = H1ParamCaseAndState.Get(workItem);
|
||||
checkFile = Path.Combine(tasksDirectory, $"{h1ParamCaseAndState.ParamCase}.md");
|
||||
markdownOld = File.Exists(checkFile) ? File.ReadAllText(checkFile) : string.Empty;
|
||||
if (markdownOld.Contains("]("))
|
||||
continue;
|
||||
weekOfYear = calendar.GetWeekOfYear(workItem.CreatedDate, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
|
||||
workItemDirectory = Path.GetFullPath(Path.Combine(rootDirectory, $"{workItem.CreatedDate:yyyy}", $"{workItem.CreatedDate:yyyy}_Week_{weekOfYear}", $"{workItem.Id}"));
|
||||
markdown = $"# {h1ParamCaseAndState.H1}{line}{line}## Id {workItem.Id}{line}{line}## Code Insiders{line}{line}- [code-insiders]({workItemDirectory}){line}";
|
||||
if (markdown != markdownOld)
|
||||
File.WriteAllText(checkFile, markdown);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSourceDirectory(string directory)
|
||||
{
|
||||
string? result = null;
|
||||
DirectoryInfo directoryInfo;
|
||||
string? checkDirectory = directory;
|
||||
string? pathRoot = Path.GetPathRoot(directory);
|
||||
for (int i = 0; i < int.MaxValue; i++)
|
||||
{
|
||||
checkDirectory = Path.GetDirectoryName(checkDirectory);
|
||||
if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot)
|
||||
break;
|
||||
directoryInfo = new(checkDirectory);
|
||||
if (string.IsNullOrEmpty(directoryInfo.LinkTarget))
|
||||
continue;
|
||||
result = directory.Replace(checkDirectory, directoryInfo.LinkTarget);
|
||||
break;
|
||||
}
|
||||
result ??= directory;
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void ExtractKanban(ILogger<Worker> logger, List<string> args)
|
||||
{
|
||||
string searchPattern = "*.json";
|
||||
string fullPath = Path.GetFullPath(args[0]);
|
||||
string sourceDirectory = GetSourceDirectory(fullPath);
|
||||
string rootDirectory = args.Count < 3 || args[2].Length < 16 ? "D:/5-Other-Small/Kanban-mestsa003/{}" : args[2];
|
||||
WriteTaskFile(sourceDirectory, rootDirectory);
|
||||
string sourceDirectoryName = Path.GetFileName(sourceDirectory);
|
||||
DirectoryInfo directoryInfo = new(Path.Combine(sourceDirectory, ".kanbn"));
|
||||
FileInfo? fileInfo = !directoryInfo.Exists ? null : new(Path.Combine(directoryInfo.FullName, $"{sourceDirectoryName}.json"));
|
||||
if (directoryInfo.Exists && fileInfo is not null && fileInfo.Exists)
|
||||
ExtractKanban(searchPattern, rootDirectory, directoryInfo, fileInfo);
|
||||
else
|
||||
logger.LogWarning("<{directoryInfo}> doesn't exist", directoryInfo.FullName);
|
||||
}
|
||||
|
||||
}
|
@ -131,6 +131,8 @@ internal static class HelperDay
|
||||
ADO2025.PI4.Helper20250114.Rename(logger, args);
|
||||
else if (args[1] == "Day-Helper-2025-01-26")
|
||||
ADO2025.PI4.Helper20250126.Move(logger, args);
|
||||
else if (args[1] == "Day-Helper-2025-02-04")
|
||||
ADO2025.PI4.Helper20250204.ExtractKanban(logger, args);
|
||||
else
|
||||
throw new Exception(appSettings.Company);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user