From 47e911527f4e868ce89939cb67123c3c2b460f90 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 3 Jul 2024 17:33:11 +0100 Subject: [PATCH] Update for fix #36 where the workItemType was enpty for items that were not comming from the template. As a bonus fix #37 with an updated list! --- .../Commands/WorkItemCloneCommand.cs | 5 +- .../Commands/WorkItemCloneCommandSettings.cs | 4 + .../Commands/WorkItemCommandBase.cs | 150 ++++++++---------- 3 files changed, 74 insertions(+), 85 deletions(-) diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs index d484092..a92f5f3 100644 --- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs +++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs @@ -37,6 +37,9 @@ public override async Task ExecuteAsync(CommandContext context, WorkItemClo // -------------------------------------------------------------- WriteOutSettings(config); + string runConfig = $"{runCache}\\config.json"; + System.IO.File.WriteAllText(runConfig, JsonConvert.SerializeObject(config, Formatting.Indented)); + if (!config.NonInteractive) { @@ -283,7 +286,7 @@ private async IAsyncEnumerable generateWorkItemsToBuildList(Lis newItem.guid = Guid.NewGuid(); newItem.hasComplexRelation = false; newItem.templateId = item.id; - newItem.workItemType = templateWorkItem.fields.SystemWorkItemType; + newItem.workItemType = templateWorkItem != null ? templateWorkItem.fields.SystemWorkItemType : "Deliverable"; newItem.fields = new Dictionary() { { "System.Title", item.fields.title }, diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs index 93f042a..8e1434c 100644 --- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs +++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs @@ -38,6 +38,10 @@ internal class WorkItemCloneCommandSettings : BaseCommandSettings [Description("The ID of the work item in the target environment that will be the parent of all created work items.")] [CommandOption("-p|--parentId|--targetParentId")] public int? targetParentId { get; set; } + [Description("This is the fallback work item type that will be used if we cant find one!")] + [CommandOption("--targetFalbackWit")] + [DefaultValue("Deliverable")] + public string? targetFalbackWit { get; set; } //------------------------------------------------ [Description("The access token for the template location")] [CommandOption("--templateAccessToken")] diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs index b55aab6..fb46733 100644 --- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs +++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs @@ -21,36 +21,37 @@ internal void CombineValuesFromConfigAndSettings(WorkItemCloneCommandSettings se config.NonInteractive = settings.NonInteractive; config.ClearCache = settings.ClearCache; config.RunName = settings.RunName != null ? settings.RunName : DateTime.Now.ToString("yyyyyMMddHHmmss"); - config.configFile = EnsureConfigFileAskIfMissing(config.configFile = settings.configFile != null ? settings.configFile : config.configFile); - config.inputJsonFile = EnsureJsonFileAskIfMissing(config.inputJsonFile = settings.inputJsonFile != null ? settings.inputJsonFile : config.inputJsonFile); - config.CachePath = EnsureCachePathAskIfMissing(config.CachePath = settings.CachePath != null ? settings.CachePath : config.CachePath); + config.configFile = EnsureFileAskIfMissing(config.configFile = settings.configFile != null ? settings.configFile : config.configFile, "Where is the config file to load?"); + config.inputJsonFile = EnsureFileAskIfMissing(config.inputJsonFile = settings.inputJsonFile != null ? settings.inputJsonFile : config.inputJsonFile, "Where is the JSON File?"); + config.CachePath = EnsureFolderAskIfMissing(config.CachePath = settings.CachePath != null ? settings.CachePath : config.CachePath, "What is the cache path?"); - config.templateOrganization = EnsureOrganizationAskIfMissing(config.templateOrganization = settings.templateOrganization != null ? settings.templateOrganization : config.templateOrganization); - config.templateProject = EnsureProjectAskIfMissing(config.templateProject = settings.templateProject != null ? settings.templateProject : config.templateProject, config.templateOrganization); - config.templateAccessToken = EnsureAccessTokenAskIfMissing(settings.templateAccessToken != null ? settings.templateAccessToken : config.templateAccessToken, config.templateOrganization); - config.templateParentId = EnsureParentIdAskIfMissing(config.templateParentId = settings.templateParentId != null ? settings.templateParentId : config.templateParentId); + config.templateOrganization = EnsureStringAskIfMissing(config.templateOrganization = settings.templateOrganization != null ? settings.templateOrganization : config.templateOrganization, "What is the template organisation?"); + config.templateProject = EnsureStringAskIfMissing(config.templateProject = settings.templateProject != null ? settings.templateProject : config.templateProject, $"What is the project on {config.templateOrganization}?"); + config.templateAccessToken = EnsureStringAskIfMissing(settings.templateAccessToken != null ? settings.templateAccessToken : config.templateAccessToken, $"What is the access token on {config.templateOrganization}?"); + config.templateParentId = EnsureIntAskIfMissing(config.templateParentId = settings.templateParentId != null ? settings.templateParentId : config.templateParentId, "Provide the template parent?"); - config.targetOrganization = EnsureOrganizationAskIfMissing(config.targetOrganization = settings.targetOrganization != null ? settings.targetOrganization : config.targetOrganization); - config.targetProject = EnsureProjectAskIfMissing(config.targetProject = settings.targetProject != null ? settings.targetProject : config.targetProject, config.targetOrganization); - config.targetAccessToken = EnsureAccessTokenAskIfMissing(settings.targetAccessToken != null ? settings.targetAccessToken : config.targetAccessToken, config.targetOrganization); - config.targetParentId = EnsureParentIdAskIfMissing(config.targetParentId = settings.targetParentId != null ? settings.targetParentId : config.targetParentId); + config.targetOrganization = EnsureStringAskIfMissing(config.targetOrganization = settings.targetOrganization != null ? settings.targetOrganization : config.targetOrganization, "What is the target organisation?"); + config.targetProject = EnsureStringAskIfMissing(config.targetProject = settings.targetProject != null ? settings.targetProject : config.targetProject, $"What is the project on {config.targetOrganization}?"); + config.targetAccessToken = EnsureStringAskIfMissing(settings.targetAccessToken != null ? settings.targetAccessToken : config.targetAccessToken, $"What is the access token on {config.targetOrganization}?"); + config.targetParentId = EnsureIntAskIfMissing(config.targetParentId = settings.targetParentId != null ? settings.targetParentId : config.targetParentId, "Provide the target parent?"); + config.targetFalbackWit = EnsureStringAskIfMissing(config.targetFalbackWit = settings.targetFalbackWit != null ? settings.targetFalbackWit : config.targetFalbackWit, "Provide the target fallback wit?"); } - internal int EnsureParentIdAskIfMissing(int? parentId) + internal int EnsureIntAskIfMissing(int? value, string message = "Provide a valid number?") { - if (parentId == null) + if (value == null) { - parentId = AnsiConsole.Prompt( - new TextPrompt("What is the parent Id?") + value = AnsiConsole.Prompt( + new TextPrompt(message) .Validate(projectId => projectId > 0 ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid parent Id[/]"))); + : ValidationResult.Error("[yellow]Invalid number[/]"))); } - return parentId.Value; + return value.Value; } private List DeserializeWorkItemList(string jsonFile) @@ -85,7 +86,7 @@ internal List DeserializeWorkItemList(WorkItemCloneCommandSettings } else { // Load new - config.inputJsonFile = EnsureJsonFileAskIfMissing(config.inputJsonFile); + config.inputJsonFile = EnsureFileAskIfMissing(config.inputJsonFile, "Where is the JSON File?"); if (!System.IO.File.Exists(config.inputJsonFile)) { AnsiConsole.MarkupLine("[red]Error:[/] No JSON file was found."); @@ -99,23 +100,10 @@ internal List DeserializeWorkItemList(WorkItemCloneCommandSettings } } - internal string EnsureJsonFileAskIfMissing(string? jsonFile) - { - if (jsonFile == null) - { - jsonFile = AnsiConsole.Prompt( - new TextPrompt("Where is the JSON File?") - .Validate(jsonFile - => !string.IsNullOrWhiteSpace(jsonFile) && System.IO.File.Exists(jsonFile) - ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid JSON file[/]"))); - } - return jsonFile; - } internal DirectoryInfo CreateOutputPath(string? outputPath) { - outputPath = EnsureCachePathAskIfMissing(outputPath); + outputPath = EnsureFolderAskIfMissing(outputPath, "What cache path should we use?"); if (!System.IO.Directory.Exists(outputPath)) { System.IO.Directory.CreateDirectory(outputPath); @@ -123,82 +111,73 @@ internal DirectoryInfo CreateOutputPath(string? outputPath) return new DirectoryInfo(outputPath); } - internal string EnsureCachePathAskIfMissing(string? outputPath) - { - if (outputPath == null) - { - outputPath = AnsiConsole.Prompt( - new TextPrompt("What is the output path?") - .Validate(outputPath - => !string.IsNullOrWhiteSpace(outputPath) - ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid output path[/]"))); - } - return outputPath; - } internal AzureDevOpsApi CreateAzureDevOpsConnection(string? accessToken, string? organization, string? project) { - organization = EnsureOrganizationAskIfMissing(organization); - project = EnsureProjectAskIfMissing(project, organization); - accessToken = EnsureAccessTokenAskIfMissing(accessToken, organization); return new AzureDevOpsApi(accessToken, organization, project); } - internal string EnsureProjectAskIfMissing(string? project, string organization) + internal string EnsureStringAskIfMissing(string value, string message) { - if (project == null) + if (value == null) { - project = AnsiConsole.Prompt( - new TextPrompt("What is the project on {organization}?") - .Validate(project - => !string.IsNullOrWhiteSpace(project) + value = AnsiConsole.Prompt( + new TextPrompt(message) + .Validate(value + => !string.IsNullOrWhiteSpace(value) ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid project[/]"))); + : ValidationResult.Error("[yellow]Invalid value[/]"))); } - return project; + return value; } - internal string EnsureOrganizationAskIfMissing(string? organization) + + internal ConfigurationSettings LoadConfigFile(string? configFile) { - if (organization == null) - { + ConfigurationSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile)); + return configSettings; + } - organization = AnsiConsole.Prompt( - new TextPrompt("What is the organization?") - .Validate(organization - => !string.IsNullOrWhiteSpace(organization) - ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid organization[/]"))); - } - return organization; + internal WorkItemCloneCommandSettings LoadWorkItemCloneCommandSettingsFromFile(string? configFile) + { + WorkItemCloneCommandSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile)); + return configSettings; } - private string EnsureAccessTokenAskIfMissing(string? accessToken, string organization) + internal string EnsureFileAskIfMissing(string? filename, string message = "What file should we load?") { - if (accessToken == null) + if (filename == null) { - accessToken = AnsiConsole.Prompt( - new TextPrompt($"Provide a valid Access Token for {organization}?") - .Validate(accessToken - => !string.IsNullOrWhiteSpace(accessToken) + filename = AnsiConsole.Prompt( + new TextPrompt("Where is the config File?") + .Validate(configFile + => !string.IsNullOrWhiteSpace(configFile) && System.IO.File.Exists(configFile) ? ValidationResult.Success() - : ValidationResult.Error("[yellow]Invalid access token[/]"))); + : ValidationResult.Error("[yellow]Invalid config file[/]"))); + } + if (!System.IO.File.Exists(filename)) + { + AnsiConsole.MarkupLine("[red]Error:[/] No file was found."); + throw new Exception(filename + " not found."); } - return accessToken; + return filename; } - internal ConfigurationSettings LoadConfigFile(string? configFile) + internal string EnsureFolderAskIfMissing(string? foldername, string message = "What folder should we use?") { - ConfigurationSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile)); - return configSettings; - } - internal WorkItemCloneCommandSettings LoadWorkItemCloneCommandSettingsFromFile(string? configFile) - { - WorkItemCloneCommandSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile)); - return configSettings; + if (foldername == null) + { + + foldername = AnsiConsole.Prompt( + new TextPrompt("Where is the folder?") + .Validate(configFile + => !string.IsNullOrWhiteSpace(configFile) && System.IO.Directory.Exists(configFile) + ? ValidationResult.Success() + : ValidationResult.Error("[yellow]Invalid config file[/]"))); + } + return foldername; } @@ -229,19 +208,22 @@ internal void WriteOutSettings(WorkItemCloneCommandSettings config) .AddColumn(new TableColumn("Setting").Alignment(Justify.Right)) .AddColumn(new TableColumn("Value")) .AddEmptyRow() - .AddRow("configFile", config.configFile != null ? config.configFile : "NOT SET") .AddRow("runName", config.RunName != null ? config.RunName : "NOT SET") + .AddEmptyRow() + .AddRow("configFile", config.configFile != null ? config.configFile : "NOT SET") .AddRow("CachePath", config.CachePath != null ? config.CachePath : "NOT SET") .AddRow("inputJsonFile", config.inputJsonFile != null ? config.inputJsonFile : "NOT SET") .AddEmptyRow() .AddRow( "templateAccessToken", "***************") .AddRow("templateOrganization", config.templateOrganization != null ? config.templateOrganization : "NOT SET") .AddRow("templateProject", config.templateProject != null ? config.templateProject : "NOT SET") + .AddRow("templateParentId", config.templateParentId != null ? config.templateParentId.ToString() : "NOT SET") .AddEmptyRow() .AddRow("targetAccessToken", "***************") .AddRow("targetOrganization", config.targetOrganization != null ? config.targetOrganization : "NOT SET") .AddRow("targetProject", config.targetProject != null ? config.targetProject : "NOT SET") .AddRow("targetParentId", config.targetParentId != null ? config.targetParentId.ToString() : "NOT SET") + .AddRow("targetFalbackWit", config.targetFalbackWit != null ? config.targetFalbackWit : "NOT SET") ); }