diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/AzureDevOps.WorkItemClone.ConsoleUI.csproj b/AzureDevOps.WorkItemClone.ConsoleUI/AzureDevOps.WorkItemClone.ConsoleUI.csproj
index 696d323..29887c9 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/AzureDevOps.WorkItemClone.ConsoleUI.csproj
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/AzureDevOps.WorkItemClone.ConsoleUI.csproj
@@ -30,6 +30,7 @@
+
@@ -40,6 +41,9 @@
Always
+
+ Never
+
Always
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/CommandSettings.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/CommandSettings.cs
index 7a73c33..446a623 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/CommandSettings.cs
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/CommandSettings.cs
@@ -6,6 +6,7 @@
using System.Text;
using Newtonsoft.Json;
using System.Threading.Tasks;
+using YamlDotNet.Serialization;
namespace AzureDevOps.WorkItemClone.ConsoleUI.Commands
{
@@ -14,7 +15,7 @@ internal class BaseCommandSettings : CommandSettings
[Description("Pre configure paramiters using this config file. Run `Init` to create it.")]
[CommandOption("--config|--configFile")]
[DefaultValue("configuration.json")]
- [JsonIgnore]
+ [JsonIgnore, YamlIgnore]
public string? configFile { get; set; }
}
}
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs
index 8c3c561..9911f8b 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommand.cs
@@ -11,6 +11,7 @@
using System.Threading.Tasks;
using System.Diagnostics.Eventing.Reader;
using AzureDevOps.WorkItemClone.Repositories;
+using YamlDotNet.Serialization;
namespace AzureDevOps.WorkItemClone.ConsoleUI.Commands
{
@@ -18,10 +19,15 @@ internal class WorkItemCloneCommand : WorkItemCommandBase ExecuteAsync(CommandContext context, WorkItemCloneCommandSettings settingsFromCmd)
{
+ if (!FileStoreCheckExtensionMatchesFormat(settingsFromCmd.configFile, settingsFromCmd.ConfigFormat))
+ {
+ AnsiConsole.MarkupLine($"[bold red]The file extension of {settingsFromCmd.configFile} does not match the format {settingsFromCmd.ConfigFormat.ToString()} selected! Please rerun with the correct format You can use --configFormat JSON or update your file to YAML[/]");
+ return -1;
+ }
WorkItemCloneCommandSettings config = null;
- if (File.Exists(settingsFromCmd.configFile))
+ if (FileStoreExist(settingsFromCmd.configFile, settingsFromCmd.ConfigFormat))
{
- config = LoadWorkItemCloneCommandSettingsFromFile(settingsFromCmd.configFile);
+ config = FileStoreLoad(settingsFromCmd.configFile, settingsFromCmd.ConfigFormat);
}
if (config == null)
{
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs
index 5578d79..1325c7f 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCloneCommandSettings.cs
@@ -1,23 +1,36 @@
using Spectre.Console.Cli;
using System.ComponentModel;
using Newtonsoft.Json;
+using YamlDotNet.Serialization;
namespace AzureDevOps.WorkItemClone.ConsoleUI.Commands
{
+ public enum ConfigFormats
+ {
+ JSON,
+ YAML
+ }
+
internal class WorkItemCloneCommandSettings : BaseCommandSettings
{
+ //------------------------------------------------
[Description("Execute with no user interaction required.")]
[CommandOption("--NonInteractive")]
- [JsonIgnore]
+ [JsonIgnore, YamlIgnore]
public bool NonInteractive { get; set; }
[Description("Clear any cache if there is any")]
[CommandOption("--ClearCache")]
- [JsonIgnore]
+ [JsonIgnore, YamlIgnore]
public bool ClearCache { get; set; }
[Description("Use this run name to execute. This will create a unique folder under the CachePath for storing run specific data and status. Defaults to yyyyyMMddHHmmss.")]
[CommandOption("--RunName")]
- [JsonIgnore]
+ [JsonIgnore, YamlIgnore]
public string? RunName { get; set; }
+ [Description("Use this run name to execute. This will create a unique folder under the CachePath for storing run specific data and status. Defaults to yyyyyMMddHHmmss.")]
+ [CommandOption("--configFormat")]
+ [DefaultValue(ConfigFormats.JSON)]
+ [JsonIgnore, YamlIgnore]
+ public ConfigFormats ConfigFormat { get; set; }
//------------------------------------------------
[CommandOption("--outputPath|--cachePath")]
[DefaultValue("./cache")]
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs
index b3e02c1..20bd564 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemCommandBase.cs
@@ -11,6 +11,9 @@
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
+using YamlDotNet.Serialization.NamingConventions;
+using YamlDotNet.Serialization;
+using Microsoft.SqlServer.Server;
namespace AzureDevOps.WorkItemClone.ConsoleUI.Commands
{
@@ -23,7 +26,7 @@ internal void CombineValuesFromConfigAndSettings(WorkItemCloneCommandSettings se
config.ClearCache = settings.ClearCache;
config.RunName = settings.RunName != null ? settings.RunName : DateTime.Now.ToString("yyyyyMMddHHmmss");
config.configFile = EnsureFileAskIfMissing(config.configFile = settings.configFile != null ? settings.configFile : config.configFile, "Where is the config file to load?");
- config.controlFile = EnsureFileAskIfMissing(config.controlFile = settings.controlFile != null ? settings.controlFile : config.controlFile, "Where is the JSON File?");
+ config.controlFile = EnsureFileAskIfMissing(config.controlFile = settings.controlFile != null ? settings.controlFile : config.controlFile, "Where is the Control File?");
config.CachePath = EnsureFolderAskIfMissing(config.CachePath = settings.CachePath != null ? settings.CachePath : config.CachePath, "What is the cache path?");
config.templateOrganization = EnsureStringAskIfMissing(config.templateOrganization = settings.templateOrganization != null ? settings.templateOrganization : config.templateOrganization, "What is the template organisation?");
@@ -137,16 +140,73 @@ internal string EnsureStringAskIfMissing(string value, string message)
}
- internal ConfigurationSettings LoadConfigFile(string? configFile)
+ public TypeToLoad FileStoreLoad(string configFile, ConfigFormats format)
{
- ConfigurationSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile));
- return configSettings;
+ TypeToLoad loadedFromFile;
+ configFile = FileStoreEnsureExtension(configFile, format);
+ if (!System.IO.File.Exists(configFile))
+ {
+ AnsiConsole.MarkupLine("[red]Error:[/] No file was found.");
+ throw new Exception(configFile + " not found.");
+ }
+ string content = System.IO.File.ReadAllText(configFile);
+ switch (format)
+ {
+ case ConfigFormats.JSON:
+ loadedFromFile = JsonConvert.DeserializeObject(content);
+ break;
+ case ConfigFormats.YAML:
+ var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build();
+ loadedFromFile = deserializer.Deserialize(content); /* compile error */
+ break;
+ default:
+ throw new Exception("Unknown format");
+ }
+ return loadedFromFile;
+ }
+
+ public bool FileStoreExist(string? configFile, ConfigFormats format)
+ {
+ return System.IO.File.Exists(FileStoreEnsureExtension(configFile, format));
+ }
+ public bool FileStoreCheckExtensionMatchesFormat(string? configFile, ConfigFormats format)
+ {
+ if (Path.GetExtension(configFile).ToLower() != $".{format.ToString().ToLower()}")
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public string FileStoreEnsureExtension(string? configFile, ConfigFormats format)
+ {
+ if (Path.GetExtension(configFile).ToLower() != $".{format.ToString().ToLower()}")
+ {
+ var original = configFile;
+ configFile = Path.ChangeExtension(configFile, format.ToString().ToLower());
+ AnsiConsole.MarkupLine($"[green]Info:[/] Changed name of {original} to {configFile} ");
+ }
+ return configFile;
}
- internal WorkItemCloneCommandSettings LoadWorkItemCloneCommandSettingsFromFile(string? configFile)
+ public string FileStoreSave(string configFile, TypeToSave content , ConfigFormats format)
{
- WorkItemCloneCommandSettings configSettings = System.Text.Json.JsonSerializer.Deserialize(System.IO.File.ReadAllText(configFile));
- return configSettings;
+ string output;
+ switch (format)
+ {
+ case ConfigFormats.JSON:
+ output = JsonConvert.SerializeObject(content, Formatting.Indented);
+ break;
+ case ConfigFormats.YAML:
+ var serializer = new SerializerBuilder().Build();
+ output = serializer.Serialize(content);
+ break;
+ default:
+ throw new Exception("Unknown format");
+ }
+ configFile = FileStoreEnsureExtension(configFile, format);
+ System.IO.File.WriteAllText(configFile, output);
+ return output;
}
internal string EnsureFileAskIfMissing(string? filename, string message = "What file should we load?")
@@ -155,17 +215,12 @@ internal string EnsureFileAskIfMissing(string? filename, string message = "What
{
filename = AnsiConsole.Prompt(
- new TextPrompt("Where is the config File?")
+ new TextPrompt(message)
.Validate(configFile
- => !string.IsNullOrWhiteSpace(configFile) && System.IO.File.Exists(configFile)
+ => !string.IsNullOrWhiteSpace(configFile)
? ValidationResult.Success()
: 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 filename;
}
@@ -197,11 +252,6 @@ internal string EnsureConfigFileAskIfMissing(string? configFile)
? ValidationResult.Success()
: ValidationResult.Error("[yellow]Invalid config file[/]")));
}
- if (!System.IO.File.Exists(configFile))
- {
- AnsiConsole.MarkupLine("[red]Error:[/] No JSON file was found.");
- throw new Exception(configFile + " not found.");
- }
return configFile;
}
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemInitCommand.cs b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemInitCommand.cs
index 91604bc..51bf7a4 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemInitCommand.cs
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Commands/WorkItemInitCommand.cs
@@ -5,6 +5,7 @@
using Spectre.Console;
using Spectre.Console.Cli;
using System.Linq;
+using Microsoft.SqlServer.Server;
namespace AzureDevOps.WorkItemClone.ConsoleUI.Commands
{
@@ -12,10 +13,14 @@ internal class WorkItemInitCommand : WorkItemCommandBase ExecuteAsync(CommandContext context, WorkItemCloneCommandSettings settings)
{
-
+ if (!FileStoreCheckExtensionMatchesFormat(settings.configFile, settings.ConfigFormat))
+ {
+ AnsiConsole.MarkupLine($"[bold red]The file extension of {settings.configFile} does not match the format {settings.ConfigFormat.ToString()} selected! Please rerun with the correct format You can use --configFormat JSON or update your file to YAML[/]");
+ return -1;
+ }
var configFile = EnsureConfigFileAskIfMissing(settings.configFile);
WorkItemCloneCommandSettings config = null;
- if (File.Exists(configFile))
+ if (FileStoreExist(configFile, settings.ConfigFormat))
{
var proceedWithSettings = AnsiConsole.Prompt(
new SelectionPrompt { Converter = value => value ? "Yes" : "No" }
@@ -23,7 +28,7 @@ public override async Task ExecuteAsync(CommandContext context, WorkItemClo
.AddChoices(true, false));
if (proceedWithSettings)
{
- config = LoadWorkItemCloneCommandSettingsFromFile(configFile);
+ config = FileStoreLoad(configFile, settings.ConfigFormat);
}
}
if (config == null)
@@ -34,12 +39,9 @@ public override async Task ExecuteAsync(CommandContext context, WorkItemClo
WriteOutSettings(config);
+ FileStoreSave(configFile, config, settings.ConfigFormat);
-
-
- System.IO.File.WriteAllText(configFile, JsonConvert.SerializeObject(config, Formatting.Indented));
-
- AnsiConsole.WriteLine("Settings saved to {configFile}!");
+ AnsiConsole.WriteLine($"Settings saved to {configFile}!");
return 0;
}
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/Properties/launchSettings.json b/AzureDevOps.WorkItemClone.ConsoleUI/Properties/launchSettings.json
index b1deeaf..4a633d6 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/Properties/launchSettings.json
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/Properties/launchSettings.json
@@ -6,7 +6,7 @@
},
"Clone": {
"commandName": "Project",
- "commandLineArgs": "clone --cachePath ..\\..\\..\\..\\..\\.cache\\ --configFile ..\\..\\..\\..\\..\\.cache\\configuration-test.json --jsonFile ..\\..\\..\\..\\..\\TestData\\tst_jsonj_export_v20.json --NonInteractive"
+ "commandLineArgs": "clone --configFormat YAML --cachePath ..\\..\\..\\..\\..\\.cache\\ --configFile ..\\..\\..\\..\\..\\.cache\\configuration-test.yaml --jsonFile ..\\..\\..\\..\\..\\TestData\\tst_jsonj_export_v20.json --NonInteractive "
},
"empty": {
"commandName": "Project"
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/configuration.json b/AzureDevOps.WorkItemClone.ConsoleUI/configuration.json
index 5ad2012..4eeb0ca 100644
--- a/AzureDevOps.WorkItemClone.ConsoleUI/configuration.json
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/configuration.json
@@ -12,5 +12,4 @@
"targetQuery": "SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AreaPath],[System.AssignedTo],[System.State] FROM workitems WHERE [System.Parent] = @projectID",
"targetQueryTitle": "Project-@RunName - @projectTitle",
"targetQueryFolder": "Shared Queries"
-
}
diff --git a/AzureDevOps.WorkItemClone.ConsoleUI/configuration.yaml b/AzureDevOps.WorkItemClone.ConsoleUI/configuration.yaml
new file mode 100644
index 0000000..745334f
--- /dev/null
+++ b/AzureDevOps.WorkItemClone.ConsoleUI/configuration.yaml
@@ -0,0 +1,40 @@
+CachePath: "./cache"
+controlFile: "ADO_TESTProjPipline_V03.json"
+targetAccessToken": null
+targetOrganization": "nkdagility-preview"
+targetProject": "ABB-Demo"
+targetParentId": 540
+templateAccessToken": null
+templateOrganization": "ABB-MO-ATE"
+templateProject": "ABB Traction Template"
+templateParentId": 212315
+targetQuery": |
+ SELECT
+ [Custom.Product],
+ [System.Title],
+ [System.Description],
+ [Custom.DeadlineDate],
+ [System.AreaPath],
+ [System.AssignedTo],
+ [System.State],
+ [Custom.Notes],
+ [System.WorkItemType],
+ [Custom.TRA_Milestone]
+ FROM workitemLinks
+ WHERE
+ (
+ [Source].[System.Id] = 000000
+ OR [Source].[System.Parent] = 000000
+ OR [Source].[System.Tags] CONTAINS 'xxxx xxxx'
+ )
+ AND (
+ [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward'
+ )
+ AND (
+ [Target].[System.Parent] = 000000
+ OR [Target].[System.Tags] CONTAINS 'xxxx xxxx'
+ )
+ ORDER BY [Custom.DeadlineDate]
+ MODE (Recursive)
+targetQueryTitle": "Project-@RunName - @projectTitle"
+targetQueryFolder": "Shared Queries"