From d1431a05a84c5458619a4311b769f008e274e048 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:10:02 +0100 Subject: [PATCH 01/11] Update builder Upgrader and compiler --- appsettings.json | 35 ++- docs/Reference/Generated/MigrationTools.xml | 20 +- .../Commands/InitMigrationCommand.cs | 5 - .../Commands/InitMigrationCommandSettings.cs | 2 +- .../Commands/MigrationConfigCommand.cs | 8 +- .../MigrationConfigCommandSettings.cs | 2 +- .../Commands/UpgradeConfigCommand.cs | 225 +-------------- src/MigrationTools.Host/MigrationToolHost.cs | 6 +- src/MigrationTools/MigrationTools.csproj | 4 + .../Options/OptionsConfiguration.cs | 4 +- .../Options/OptionsConfigurationBuilder.cs | 4 +- .../Options/OptionsConfigurationUpgrader.cs | 257 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 4 + 13 files changed, 325 insertions(+), 251 deletions(-) create mode 100644 src/MigrationTools/Options/OptionsConfigurationUpgrader.cs diff --git a/appsettings.json b/appsettings.json index 256b4af6f..6bc670e93 100644 --- a/appsettings.json +++ b/appsettings.json @@ -59,7 +59,7 @@ "FieldMappingTool": { "Enabled": false, "FieldMaps": [], - "FieldMapDefaults" : { + "FieldMapDefaults": { "ApplyTo": [ "*" ] } }, @@ -428,6 +428,39 @@ "SourceName": "Source", "TargetName": "Target" } + }, + "Infrastructure": { + "ClassNameChangeMappings": { + "WorkItemMigration": "TfsWorkItemMigrationProcessor", + "WorkItemMigrationContext": "TfsWorkItemMigrationProcessor", + "TfsTeamProjectConfig": "TfsTeamProjectEndpoint", + "WorkItemGitRepoMappingTool": "TfsGitRepositoryTool", + "WorkItemFieldMappingTool": "FieldMappingTool", + "CreateTeamFolders": "TfsCreateTeamFoldersProcessor", + "ExportProfilePicture": "TfsExportProfilePictureFromADProcessor", + "ExportProfilePictureFromAD": "TfsExportProfilePictureFromADProcessor", + "ImportProfilePicture": "TfsImportProfilePictureProcessor", + "ImportProfilePictureFromAD": "TfsImportProfilePictureProcessor", + "ExportUsersForMapping": "TfsExportUsersForMappingProcessor", + "ExportUsersForMappingContext": "TfsExportUsersForMappingProcessor", + "ExportTeamListProcessor": "TfsExportTeamListProcessor", + "ExportTeamList": "TfsExportTeamListProcessor", + "ExportUsersForMappingProcessor": "TfsExportUsersForMappingProcessor", + "TestConfigurationsMigrationProcessor": "TfsTestConfigurationsMigrationProcessor", + "TestConfigurationsMigrationContext": "TfsTestConfigurationsMigrationProcessor", + "TestPlansAndSuitesMigrationProcessor": "TfsTestPlansAndSuitesMigrationProcessor", + "TestPlansAndSuitesMigrationContext": "TfsTestPlansAndSuitesMigrationProcessor", + "TestVariablesMigrationProcessor": "TfsTestVariablesMigrationProcessor", + "TestVariablesMigrationContext": "TfsTestVariablesMigrationProcessor", + "WorkItemBulkEditProcessor": "TfsWorkItemBulkEditProcessor", + "WorkItemUpdate": "TfsWorkItemBulkEditProcessor", + "WorkItemDelete": "TfsWorkItemDeleteProcessor", + "WorkItemDeleteProcessor": "TfsWorkItemDeleteProcessor", + "WorkItemPostProcessingContext": "TfsWorkItemOverwriteProcessor", + "WorkItemPostProcessingProcessor": "TfsWorkItemOverwriteProcessor", + "WorkItemUpdateAreasAsTagsContext": "TfsWorkItemOverwriteProcessor", + "WorkItemUpdateAreasAsTagsProcessor": "TfsWorkItemOverwriteProcessor" + } } } } diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 30eecbc1c..de76a6bdd 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -258,37 +258,37 @@ - => @"fix/WITDataStore64-missing" + => @"main" - => @"bafb2485" + => @"b8619076" - => @"bafb2485ee0926f0443ede89e1e0204fc50f8aae" + => @"b86190765c571122bf2123e6c77ccab307b81025" - => @"2024-09-04T08:45:13+01:00" + => @"2024-09-04T09:21:49+01:00" - => @"2" + => @"0" - => @"v16.0.0-Preview.7-2-gbafb2485" + => @"v16.0.0-Preview.9" - => @"v16.0.0-Preview.7" + => @"v16.0.0-Preview.9" @@ -318,17 +318,17 @@ - => @"2" + => @"0" - => @"Preview.7" + => @"Preview.9" - => @"-Preview.7" + => @"-Preview.9" diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs index 53b73c167..a930177b4 100644 --- a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs +++ b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs @@ -57,11 +57,6 @@ internal override async Task ExecuteInternalAsync(CommandContext context, I } if (!File.Exists(configFile)) { - var configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .Build(); - _logger.LogInformation("Populating config with {Options}", settings.Options.ToString()); OptionsConfiguration optionsBuilder = Services.GetService(); diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs index e80fa9038..b7a98a092 100644 --- a/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs +++ b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs @@ -21,7 +21,7 @@ public enum OptionsMode { Reference = 0, WorkItemTracking = 1, - Basic = 4, + Basic = 3, PipelineProcessor = 5 } } \ No newline at end of file diff --git a/src/MigrationTools.Host/Commands/MigrationConfigCommand.cs b/src/MigrationTools.Host/Commands/MigrationConfigCommand.cs index 0b1fe5dfa..46b2fdfa5 100644 --- a/src/MigrationTools.Host/Commands/MigrationConfigCommand.cs +++ b/src/MigrationTools.Host/Commands/MigrationConfigCommand.cs @@ -12,14 +12,14 @@ namespace MigrationTools.Host.Commands { - internal class MigrationConfigCommand : AsyncCommand + internal class ConfigurationBuilderCommand : AsyncCommand { private IServiceProvider _services; private readonly ILogger _logger; private readonly ITelemetryLogger Telemetery; private readonly IHostApplicationLifetime _appLifetime; - public MigrationConfigCommand( + public ConfigurationBuilderCommand( IServiceProvider services, ILogger logger, ITelemetryLogger telemetryLogger, @@ -32,7 +32,7 @@ public MigrationConfigCommand( } - public override async Task ExecuteAsync(CommandContext context, MigrationConfigCommandSettings settings) + public override async Task ExecuteAsync(CommandContext context, ConfigurationBuilderCommandSettings settings) { int _exitCode; @@ -47,7 +47,7 @@ public override async Task ExecuteAsync(CommandContext context, MigrationCo // Load configuration var configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) + .SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile(configFile, optional: true, reloadOnChange: true) .Build(); diff --git a/src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs b/src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs index 553c313e2..02a51ac31 100644 --- a/src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs +++ b/src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs @@ -3,7 +3,7 @@ namespace MigrationTools.Host.Commands { - internal class MigrationConfigCommandSettings : CommandSettingsBase + internal class ConfigurationBuilderCommandSettings : CommandSettingsBase { } diff --git a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs index 81112a230..89d94de50 100644 --- a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs +++ b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs @@ -37,7 +37,7 @@ internal class UpgradeConfigCommand : CommandBase private readonly ILogger _logger; - private static Dictionary classNameChangeLog = new Dictionary(); + public UpgradeConfigCommand(IHostApplicationLifetime appLifetime, IServiceProvider services, IDetectOnlineService detectOnlineService, IDetectVersionService2 detectVersionService, ILogger> logger, ITelemetryLogger telemetryLogger, IMigrationToolVersion migrationToolVersion, IConfiguration configuration, ActivitySource activitySource) : base(appLifetime, services, detectOnlineService, detectVersionService, logger, telemetryLogger, migrationToolVersion, configuration, activitySource) { @@ -55,81 +55,11 @@ internal override async Task ExecuteInternalAsync(CommandContext context, U } _logger.LogInformation("ConfigFile: {configFile}", configFile); - //// Load configuration - //var configuration = new ConfigurationBuilder() - // .SetBasePath(Directory.GetCurrentDirectory()) - // .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - // .AddJsonFile(configFile, optional: true, reloadOnChange: true) - // .Build(); + OptionsConfiguration optionsBuilder = Services.GetRequiredService(); + OptionsConfigurationUpgrader optionsUpgrader = Services.GetRequiredService(); - classNameChangeLog.Add("WorkItemMigrationContext", "TfsWorkItemMigrationProcessor"); - classNameChangeLog.Add("TfsTeamProjectConfig", "TfsTeamProjectEndpoint"); - classNameChangeLog.Add("WorkItemGitRepoMappingTool", "TfsGitRepositoryTool"); - classNameChangeLog.Add("WorkItemFieldMappingTool", "FieldMappingTool"); - classNameChangeLog.Add("CreateTeamFolders", "TfsCreateTeamFoldersProcessor"); - classNameChangeLog.Add("ExportProfilePicture", "TfsExportProfilePictureFromADProcessor"); - classNameChangeLog.Add("ExportProfilePictureFromAD", "TfsExportProfilePictureFromADProcessor"); - classNameChangeLog.Add("ImportProfilePicture", "TfsImportProfilePictureProcessor"); - classNameChangeLog.Add("ImportProfilePictureFromAD", "TfsImportProfilePictureProcessor"); - classNameChangeLog.Add("ExportUsersForMapping", "TfsExportUsersForMappingProcessor"); - classNameChangeLog.Add("ExportUsersForMappingContext", "TfsExportUsersForMappingProcessor"); - classNameChangeLog.Add("ExportTeamListProcessor", "TfsExportTeamListProcessor"); - classNameChangeLog.Add("ExportTeamList", "TfsExportTeamListProcessor"); - classNameChangeLog.Add("ExportUsersForMappingProcessor", "TfsExportUsersForMappingProcessor"); - classNameChangeLog.Add("ExportUsersForMapping", "TfsExportUsersForMappingProcessor"); - classNameChangeLog.Add("TestConfigurationsMigrationProcessor", "TfsTestConfigurationsMigrationProcessor"); - classNameChangeLog.Add("TestConfigurationsMigrationContext", "TfsTestConfigurationsMigrationProcessor"); - classNameChangeLog.Add("TestPlansAndSuitesMigrationProcessor", "TfsTestPlansAndSuitesMigrationProcessor"); - classNameChangeLog.Add("TestPlansAndSuitesMigrationContext", "TfsTestPlansAndSuitesMigrationProcessor"); - classNameChangeLog.Add("TestVariablesMigrationProcessor", "TfsTestVariablesMigrationProcessor"); - classNameChangeLog.Add("TestVariablesMigrationContext", "TfsTestVariablesMigrationProcessor"); - classNameChangeLog.Add("WorkItemBulkEditProcessor", "TfsWorkItemBulkEditProcessor"); - classNameChangeLog.Add("WorkItemUpdate", "TfsWorkItemBulkEditProcessor"); - classNameChangeLog.Add("WorkItemDelete", "TfsWorkItemDeleteProcessor"); - classNameChangeLog.Add("WorkItemDeleteProcessor", "TfsWorkItemDeleteProcessor"); - classNameChangeLog.Add("WorkItemPostProcessingContext", "TfsWorkItemOverwriteProcessor"); - classNameChangeLog.Add("WorkItemPostProcessingProcessor", "TfsWorkItemOverwriteProcessor"); - classNameChangeLog.Add("WorkItemUpdateAreasAsTagsContext", "TfsWorkItemOverwriteProcessor"); - classNameChangeLog.Add("WorkItemUpdateAreasAsTagsProcessor", "TfsWorkItemOverwriteProcessor"); + optionsUpgrader.UpgradeConfiguration(optionsBuilder); - - - OptionsConfiguration optionsBuilder = Services.GetService(); - - var schemaVersion = VersionOptions.ConfigureOptions.GetMigrationConfigVersion(Configuration); - CommandActivity?.AddTag("SchemaVersion", schemaVersion.schema.ToString()); - CommandActivity.AddEvent(new ActivityEvent($"UpgradeConfigCommand.{schemaVersion.schema.ToString()}")); - switch (schemaVersion.schema) - { - case MigrationConfigSchema.v1: - case MigrationConfigSchema.v150: - CommandActivity.AddEvent(new ActivityEvent("UpgradeConfigCommand.v150")); - // ChangeSetMappingFile - optionsBuilder.AddOption(ParseV1TfsChangeSetMappingToolOptions(Configuration)); - optionsBuilder.AddOption(ParseV1TfsGitRepoMappingOptions(Configuration)); - optionsBuilder.AddOption(ParseV1FieldMaps(Configuration)); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "Processors", "$type")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "CommonEnrichersConfig", "$type")); - if (!IsSectionNullOrEmpty(Configuration.GetSection("Source")) || !IsSectionNullOrEmpty(Configuration.GetSection("Target"))) - { - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Source", "$type"), "Source"); - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Target", "$type"), "Target"); - } - else - { - optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Source"), "Source"); - optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Target"), "Target"); - } - break; - case MigrationConfigSchema.v160: - - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Source", "EndpointType"), "Source"); - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Target", "EndpointType"), "Target"); - optionsBuilder.AddOption(ParseSectionListWithPathAsTypeToOption(Configuration, "MigrationTools:CommonTools")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:CommonTools:FieldMappingTool:FieldMaps", "FieldMapType")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:Processors", "ProcessorType")); - break; - } string json = optionsBuilder.Build(); configFile = AddSuffixToFileName(configFile, "-upgraded"); File.WriteAllText(configFile, json); @@ -138,66 +68,6 @@ internal override async Task ExecuteInternalAsync(CommandContext context, U return _exitCode; } - private IOptions ParseSectionCollectionWithPathAsTypeToOption(IConfiguration configuration, string path, string filter) - { - var optionsConfigList = configuration.GetSection(path); - var optionTypeString = GetLastSegment(path); - IOptions option = null; - foreach (var childSection in optionsConfigList.GetChildren()) - { - if (childSection.GetValue("Name") == filter) - { - option = GetOptionFromTypeString(configuration, childSection, optionTypeString); - - } - } - return option; - } - - private List ParseSectionListWithPathAsTypeToOption(IConfiguration configuration, string path) - { - var optionsConfigList = configuration.GetSection(path); - List options = new List(); - foreach (var childSection in optionsConfigList.GetChildren()) - { - var optionTypeString = childSection.Key; - var option = GetOptionFromTypeString(configuration, childSection, optionTypeString); - if (option != null) - { - options.Add(option); - } - } - return options; - } - - private List ParseSectionCollectionWithTypePropertyNameToList(IConfiguration configuration, string path, string typePropertyName) - { - var targetSection = configuration.GetSection(path); - List options = new List(); - foreach (var childSection in targetSection.GetChildren()) - { - var optionTypeString = childSection.GetValue(typePropertyName); - var newOptionTypeString = ParseOptionsType(optionTypeString); - _logger.LogInformation("Upgrading {group} item {old} to {new}", path, optionTypeString, newOptionTypeString); - var option = GetOptionWithDefaults(configuration, newOptionTypeString); - childSection.Bind(option); - options.Add(option); - } - - return options; - } - - private List ParseV1FieldMaps(IConfiguration configuration) - { - List options = new List(); - _logger.LogInformation("Upgrading {old} to {new}", "FieldMaps", "FieldMappingToolOptions"); - var toolOption = GetOptionWithDefaults(configuration, ParseOptionsType("FieldMappingToolOptions")); - toolOption.Enabled = true; - options.Add(toolOption); - // parese FieldMaps - options.AddRange(ParseSectionCollectionWithTypePropertyNameToList(configuration, "FieldMaps", "$type")); - return options; - } static string AddSuffixToFileName(string filePath, string suffix) { @@ -217,93 +87,6 @@ static string AddSuffixToFileName(string filePath, string suffix) return Path.Combine(directory, newFileName); } - private IOptions ParseSectionWithTypePropertyNameToOptions(IConfiguration configuration, string path, string typePropertyName) - { - var optionsConfig = configuration.GetSection(path); - var optionTypeString = optionsConfig.GetValue(typePropertyName); - IOptions sourceOptions = GetOptionFromTypeString(configuration, optionsConfig, optionTypeString); - return sourceOptions; - } - - private IOptions GetOptionFromTypeString(IConfiguration configuration, IConfigurationSection optionsConfig, string optionTypeString) - { - var newOptionTypeString = ParseOptionsType(optionTypeString); - _logger.LogInformation("Upgrading to {old} to {new}", optionTypeString, newOptionTypeString); - IOptions sourceOptions; - sourceOptions = GetOptionWithDefaults(configuration, newOptionTypeString); - optionsConfig.Bind(sourceOptions); - return sourceOptions; - } - - private IOptions GetOptionWithDefaults(IConfiguration configuration, string optionTypeString) - { - IOptions option; - optionTypeString = ParseOptionsType(optionTypeString); - var optionType = AppDomain.CurrentDomain.GetMigrationToolsTypes().WithInterface().FirstOrDefault(t => t.Name.StartsWith(optionTypeString, StringComparison.InvariantCultureIgnoreCase)); - if (optionType == null) - { - _logger.LogWarning("Could not find type {optionTypeString}", optionTypeString); - return null; - } - option = (IOptions)Activator.CreateInstance(optionType); - var defaultConfig = configuration.GetSection(option.ConfigurationMetadata.PathToDefault); - defaultConfig.Bind(option); - return option; - } - - private IOptions ParseV1TfsChangeSetMappingToolOptions(IConfiguration configuration) - { - _logger.LogInformation("Upgrading {old} to {new}", "ChangeSetMappingFile", "TfsChangeSetMappingTool"); - var changeSetMappingOptions = configuration.GetValue("ChangeSetMappingFile"); - var properties = new Dictionary - { - { "ChangeSetMappingFile", changeSetMappingOptions } - }; - var option = (IOptions)OptionsBinder.BindToOptions("TfsChangeSetMappingToolOptions", properties, classNameChangeLog); - option.Enabled = true; - return option; - } - - private IOptions ParseV1TfsGitRepoMappingOptions(IConfiguration configuration) - { - _logger.LogInformation("Upgrading {old} to {new}", "GitRepoMapping", "TfsGitRepoMappingTool"); - var data = configuration.GetValue>("GitRepoMapping"); - var properties = new Dictionary - { - { "Mappings", data } - }; - var option = (IOptions)OptionsBinder.BindToOptions("TfsGitRepositoryToolOptions", properties, classNameChangeLog); - option.Enabled = true; - return option; - } - - static string ParseOptionsType(string optionTypeString) - { - if (classNameChangeLog.ContainsKey(optionTypeString)) - { - optionTypeString = classNameChangeLog[optionTypeString]; - } - return RemoveSuffix(optionTypeString); - } - - static string RemoveSuffix(string input) - { - // Use regex to replace "Config" or "Options" only if they appear at the end of the string - return Regex.Replace(input, "(s|Config|Options)$", ""); - } - - static string GetLastSegment(string path) - { - // Split the path by colon and return the last segment - string[] segments = path.Split(':'); - return segments[segments.Length - 1]; - } - - static bool IsSectionNullOrEmpty(IConfigurationSection section) - { - // Check if the section exists and has a value or children - return !section.Exists() || string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any(); - } } diff --git a/src/MigrationTools.Host/MigrationToolHost.cs b/src/MigrationTools.Host/MigrationToolHost.cs index d1cd0d66d..20707f1fb 100644 --- a/src/MigrationTools.Host/MigrationToolHost.cs +++ b/src/MigrationTools.Host/MigrationToolHost.cs @@ -120,14 +120,12 @@ public static IHostBuilder CreateDefaultBuilder(string[] args, Action("init") .WithDescription("Creates an default configuration file") - .WithExample("init -options Basic") - .WithExample("init -options WorkItemTracking ") - .WithExample("init -options Reference "); + .WithExample("init -options Basic"); config.AddCommand("upgrade") .WithDescription("Atempts to upgrade your config from the old version to the new one. For each object we will load the defaults, then apply your config. This will only bring accross valid settings. This is 'best effort' and you will need to check all the values as we have changed a lot!") .WithExample("upgrade -config \"configuration.json\""); - //config.AddCommand("config") + //config.AddCommand("builder") // .WithDescription("Creates or edits a configuration file") // .WithExample("config -config \"configuration.json\""); diff --git a/src/MigrationTools/MigrationTools.csproj b/src/MigrationTools/MigrationTools.csproj index e80a3bcd5..75dccc842 100644 --- a/src/MigrationTools/MigrationTools.csproj +++ b/src/MigrationTools/MigrationTools.csproj @@ -15,6 +15,10 @@ true + + + + false diff --git a/src/MigrationTools/Options/OptionsConfiguration.cs b/src/MigrationTools/Options/OptionsConfiguration.cs index b19433f62..db41155a5 100644 --- a/src/MigrationTools/Options/OptionsConfiguration.cs +++ b/src/MigrationTools/Options/OptionsConfiguration.cs @@ -168,7 +168,7 @@ private JObject AddNamedOptionToConfig(IConfiguration configuration, JObject con { var hardPath = $"MigrationTools:Endpoints:{key}"; logger.LogDebug("Building Option: {item} to {hardPath}", option.GetType().Name, hardPath); - configJson = OptionsConfigurationBuilder.AddOptionsToConfiguration(configJson, option, hardPath, true); + configJson = OptionsConfigurationCompiler.AddOptionsToConfiguration(configJson, option, hardPath, true); } catch (Exception) @@ -195,7 +195,7 @@ private JObject AddOptionToConfig(IConfiguration configuration, JObject configJs try { logger.LogDebug("Building Option: {item} to {path}", option.GetType().Name, option.ConfigurationMetadata.PathToInstance); - configJson = OptionsConfigurationBuilder.AddOptionsToConfiguration(configJson, option, false); + configJson = OptionsConfigurationCompiler.AddOptionsToConfiguration(configJson, option, false); } catch (Exception) diff --git a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs index 21ae3b151..e6ea0aed5 100644 --- a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs +++ b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs @@ -9,8 +9,8 @@ namespace MigrationTools.Options { -public static class OptionsConfigurationBuilder -{ +public static class OptionsConfigurationCompiler + { public static IOptions LoadConfiguration(string filePath, IOptions options, bool isCollection = false) { var optionsConfig = options.ConfigurationMetadata; diff --git a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs new file mode 100644 index 000000000..fbf50a00b --- /dev/null +++ b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.Services.Common; +using MigrationTools._EngineV1.Configuration; +using MigrationTools._EngineV1.Containers; +using MigrationTools.Options; +using MigrationTools.Processors; +using MigrationTools.Processors.Infrastructure; +using MigrationTools.Services; +using Newtonsoft.Json.Linq; + +namespace MigrationTools.Options +{ + public class OptionsConfigurationUpgrader + { + public IServiceProvider Services { get; } + public IConfiguration Configuration { get; } + + readonly ILogger _logger; + + + private static Dictionary classNameChangeLog = new Dictionary(); + + private List catalogue; + + public OptionsConfigurationUpgrader( + IConfiguration configuration, + ILogger logger, + ITelemetryLogger telemetryLogger, IServiceProvider services) + { + this.Services = services; + this.Configuration = configuration; + this._logger = logger; + catalogue = AppDomain.CurrentDomain.GetMigrationToolsTypes().WithInterface().ToList(); + configuration.GetSection("MigrationTools:Infrastructure:ClassNameChangeMappings").Bind(classNameChangeLog); + if (classNameChangeLog.Count == 0) + { + _logger.LogWarning("No ClassNameChangeMappings found in configuration! Check the value of MigrationTools:Infrastructure:ClassNameChangeMappings in the appsettings.json file!"); + } + } + + public OptionsConfiguration UpgradeConfiguration(OptionsConfiguration optionsBuilder) + { + using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("OptionsConfigurationUpgrader::UpgradeConfiguration")) + { + + + if (optionsBuilder == null) + { + optionsBuilder = this.Services.GetService(); + } + + var schemaVersion = VersionOptions.ConfigureOptions.GetMigrationConfigVersion(Configuration); + activity?.AddTag("SchemaVersion", schemaVersion.schema.ToString()); + activity.AddEvent(new ActivityEvent($"UpgradeConfigCommand.{schemaVersion.schema.ToString()}")); + switch (schemaVersion.schema) + { + case MigrationConfigSchema.v1: + case MigrationConfigSchema.v150: + activity.AddEvent(new ActivityEvent("UpgradeConfigCommand.v150")); + // ChangeSetMappingFile + optionsBuilder.AddOption(ParseV1TfsChangeSetMappingToolOptions(Configuration)); + optionsBuilder.AddOption(ParseV1TfsGitRepoMappingOptions(Configuration)); + optionsBuilder.AddOption(ParseV1FieldMaps(Configuration)); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "Processors", "$type")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "CommonEnrichersConfig", "$type")); + if (!IsSectionNullOrEmpty(Configuration.GetSection("Source")) || !IsSectionNullOrEmpty(Configuration.GetSection("Target"))) + { + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Source", "$type"), "Source"); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Target", "$type"), "Target"); + } + else + { + optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Source"), "Source"); + optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Target"), "Target"); + } + break; + case MigrationConfigSchema.v160: + + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Source", "EndpointType"), "Source"); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Target", "EndpointType"), "Target"); + optionsBuilder.AddOption(ParseSectionListWithPathAsTypeToOption(Configuration, "MigrationTools:CommonTools")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:CommonTools:FieldMappingTool:FieldMaps", "FieldMapType")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:Processors", "ProcessorType")); + break; + } + return optionsBuilder; + } + } + + private IOptions ParseSectionCollectionWithPathAsTypeToOption(IConfiguration configuration, string path, string filter) + { + var optionsConfigList = configuration.GetSection(path); + var optionTypeString = GetLastSegment(path); + IOptions option = null; + foreach (var childSection in optionsConfigList.GetChildren()) + { + if (childSection.GetValue("Name") == filter) + { + option = GetOptionFromTypeString(configuration, childSection, optionTypeString); + + } + } + return option; + } + + private List ParseSectionListWithPathAsTypeToOption(IConfiguration configuration, string path) + { + var optionsConfigList = configuration.GetSection(path); + List options = new List(); + foreach (var childSection in optionsConfigList.GetChildren()) + { + var optionTypeString = childSection.Key; + var option = GetOptionFromTypeString(configuration, childSection, optionTypeString); + if (option != null) + { + options.Add(option); + } + } + return options; + } + + private List ParseSectionCollectionWithTypePropertyNameToList(IConfiguration configuration, string path, string typePropertyName) + { + var targetSection = configuration.GetSection(path); + List options = new List(); + foreach (var childSection in targetSection.GetChildren()) + { + var optionTypeString = childSection.GetValue(typePropertyName); + var newOptionTypeString = ParseOptionsType(optionTypeString); + _logger.LogInformation("Upgrading {group} item {old} to {new}", path, optionTypeString, newOptionTypeString); + var option = GetOptionWithDefaults(configuration, newOptionTypeString); + childSection.Bind(option); + options.Add(option); + } + + return options; + } + + private List ParseV1FieldMaps(IConfiguration configuration) + { + List options = new List(); + _logger.LogInformation("Upgrading {old} to {new}", "FieldMaps", "FieldMappingToolOptions"); + var toolOption = GetOptionWithDefaults(configuration, ParseOptionsType("FieldMappingToolOptions")); + toolOption.Enabled = true; + options.Add(toolOption); + // parese FieldMaps + options.AddRange(ParseSectionCollectionWithTypePropertyNameToList(configuration, "FieldMaps", "$type")); + return options; + } + + + private IOptions ParseSectionWithTypePropertyNameToOptions(IConfiguration configuration, string path, string typePropertyName) + { + var optionsConfig = configuration.GetSection(path); + var optionTypeString = optionsConfig.GetValue(typePropertyName); + IOptions sourceOptions = GetOptionFromTypeString(configuration, optionsConfig, optionTypeString); + return sourceOptions; + } + + private IOptions GetOptionFromTypeString(IConfiguration configuration, IConfigurationSection optionsConfig, string optionTypeString) + { + var newOptionTypeString = ParseOptionsType(optionTypeString); + _logger.LogInformation("Upgrading to {old} to {new}", optionTypeString, newOptionTypeString); + IOptions sourceOptions; + sourceOptions = GetOptionWithDefaults(configuration, newOptionTypeString); + optionsConfig.Bind(sourceOptions); + return sourceOptions; + } + + private IOptions GetOptionWithDefaults(IConfiguration configuration, string optionTypeString) + { + IOptions option; + optionTypeString = ParseOptionsType(optionTypeString); + var optionType = AppDomain.CurrentDomain.GetMigrationToolsTypes().WithInterface().FirstOrDefault(t => t.Name.StartsWith(optionTypeString, StringComparison.InvariantCultureIgnoreCase)); + if (optionType == null) + { + _logger.LogWarning("Could not find type {optionTypeString}", optionTypeString); + return null; + } + option = (IOptions)Activator.CreateInstance(optionType); + var defaultConfig = configuration.GetSection(option.ConfigurationMetadata.PathToDefault); + defaultConfig.Bind(option); + return option; + } + + private IOptions ParseV1TfsChangeSetMappingToolOptions(IConfiguration configuration) + { + _logger.LogInformation("Upgrading {old} to {new}", "ChangeSetMappingFile", "TfsChangeSetMappingTool"); + var changeSetMappingOptions = configuration.GetValue("ChangeSetMappingFile"); + var properties = new Dictionary + { + { "ChangeSetMappingFile", changeSetMappingOptions } + }; + var option = (IOptions)OptionsBinder.BindToOptions("TfsChangeSetMappingToolOptions", properties, classNameChangeLog); + option.Enabled = true; + return option; + } + + private IOptions ParseV1TfsGitRepoMappingOptions(IConfiguration configuration) + { + _logger.LogInformation("Upgrading {old} to {new}", "GitRepoMapping", "TfsGitRepoMappingTool"); + var data = configuration.GetValue>("GitRepoMapping"); + var properties = new Dictionary + { + { "Mappings", data } + }; + var option = (IOptions)OptionsBinder.BindToOptions("TfsGitRepositoryToolOptions", properties, classNameChangeLog); + option.Enabled = true; + return option; + } + + static string ParseOptionsType(string optionTypeString) + { + if (classNameChangeLog.ContainsKey(optionTypeString)) + { + optionTypeString = classNameChangeLog[optionTypeString]; + } + return RemoveSuffix(optionTypeString); + } + + static string RemoveSuffix(string input) + { + // Use regex to replace "Config" or "Options" only if they appear at the end of the string + return Regex.Replace(input, "(s|Config|Options)$", ""); + } + + static string GetLastSegment(string path) + { + // Split the path by colon and return the last segment + string[] segments = path.Split(':'); + return segments[segments.Length - 1]; + } + + static bool IsSectionNullOrEmpty(IConfigurationSection section) + { + // Check if the section exists and has a value or children + return !section.Exists() || string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any(); + } + } +} diff --git a/src/MigrationTools/ServiceCollectionExtensions.cs b/src/MigrationTools/ServiceCollectionExtensions.cs index d937eb13c..a5dc024e9 100644 --- a/src/MigrationTools/ServiceCollectionExtensions.cs +++ b/src/MigrationTools/ServiceCollectionExtensions.cs @@ -28,7 +28,11 @@ public static OptionsBuilder AddMigrationToolsOptions(this I public static void AddMigrationToolServices(this IServiceCollection context, IConfiguration configuration, string configFile = "configuration.json") { + // Infra context.AddSingleton(); + context.AddSingleton(); + + context.AddConfiguredEndpoints(configuration); //Containers context.AddTransient(); From 2fc406443fd9372200d117e0dd124d16579a4638 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:25:39 +0100 Subject: [PATCH 02/11] Update rena,e --- ...onsConfigurationBuilder.cs => OptionsConfigurationCompiler.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/MigrationTools/Options/{OptionsConfigurationBuilder.cs => OptionsConfigurationCompiler.cs} (100%) diff --git a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs b/src/MigrationTools/Options/OptionsConfigurationCompiler.cs similarity index 100% rename from src/MigrationTools/Options/OptionsConfigurationBuilder.cs rename to src/MigrationTools/Options/OptionsConfigurationCompiler.cs From dda227cab7f56b5ea1885d2ec19f5f0d5a84e02c Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:26:12 +0100 Subject: [PATCH 03/11] Update --- docs/Reference/Generated/MigrationTools.xml | 14 +++++++------- .../Commands/InitMigrationCommand.cs | 2 +- .../Commands/UpgradeConfigCommand.cs | 2 +- src/MigrationTools/Options/OptionsConfiguration.cs | 6 +++--- .../Options/OptionsConfigurationUpgrader.cs | 6 +++--- src/MigrationTools/ServiceCollectionExtensions.cs | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index de76a6bdd..3515987af 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -258,32 +258,32 @@ - => @"main" + => @"topic/config-builder" - => @"b8619076" + => @"2fc40644" - => @"b86190765c571122bf2123e6c77ccab307b81025" + => @"2fc406443fd9372200d117e0dd124d16579a4638" - => @"2024-09-04T09:21:49+01:00" + => @"2024-09-04T11:25:39+01:00" - => @"0" + => @"2" - => @"v16.0.0-Preview.9" + => @"v16.0.0-Preview.9-2-g2fc40644" @@ -318,7 +318,7 @@ - => @"0" + => @"2" diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs index a930177b4..1b8c75810 100644 --- a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs +++ b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs @@ -59,7 +59,7 @@ internal override async Task ExecuteInternalAsync(CommandContext context, I { _logger.LogInformation("Populating config with {Options}", settings.Options.ToString()); - OptionsConfiguration optionsBuilder = Services.GetService(); + OptionsConfigurationBuilder optionsBuilder = Services.GetService(); switch (settings.Options) { diff --git a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs index 89d94de50..07a6cfbf9 100644 --- a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs +++ b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs @@ -55,7 +55,7 @@ internal override async Task ExecuteInternalAsync(CommandContext context, U } _logger.LogInformation("ConfigFile: {configFile}", configFile); - OptionsConfiguration optionsBuilder = Services.GetRequiredService(); + OptionsConfigurationBuilder optionsBuilder = Services.GetRequiredService(); OptionsConfigurationUpgrader optionsUpgrader = Services.GetRequiredService(); optionsUpgrader.UpgradeConfiguration(optionsBuilder); diff --git a/src/MigrationTools/Options/OptionsConfiguration.cs b/src/MigrationTools/Options/OptionsConfiguration.cs index db41155a5..baf16fd45 100644 --- a/src/MigrationTools/Options/OptionsConfiguration.cs +++ b/src/MigrationTools/Options/OptionsConfiguration.cs @@ -17,7 +17,7 @@ namespace MigrationTools.Options { - public class OptionsConfiguration + public class OptionsConfigurationBuilder { readonly ILogger logger; readonly IConfiguration configuration; @@ -27,9 +27,9 @@ public class OptionsConfiguration private List catalogue; - public OptionsConfiguration( + public OptionsConfigurationBuilder( IConfiguration configuration, - ILogger logger, + ILogger logger, ITelemetryLogger telemetryLogger) { this.configuration = configuration; diff --git a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs index fbf50a00b..f2a10a8d8 100644 --- a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs +++ b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs @@ -41,7 +41,7 @@ public class OptionsConfigurationUpgrader public OptionsConfigurationUpgrader( IConfiguration configuration, - ILogger logger, + ILogger logger, ITelemetryLogger telemetryLogger, IServiceProvider services) { this.Services = services; @@ -55,7 +55,7 @@ public OptionsConfigurationUpgrader( } } - public OptionsConfiguration UpgradeConfiguration(OptionsConfiguration optionsBuilder) + public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuilder optionsBuilder) { using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("OptionsConfigurationUpgrader::UpgradeConfiguration")) { @@ -63,7 +63,7 @@ public OptionsConfiguration UpgradeConfiguration(OptionsConfiguration optionsBui if (optionsBuilder == null) { - optionsBuilder = this.Services.GetService(); + optionsBuilder = this.Services.GetService(); } var schemaVersion = VersionOptions.ConfigureOptions.GetMigrationConfigVersion(Configuration); diff --git a/src/MigrationTools/ServiceCollectionExtensions.cs b/src/MigrationTools/ServiceCollectionExtensions.cs index a5dc024e9..40e0fd1a4 100644 --- a/src/MigrationTools/ServiceCollectionExtensions.cs +++ b/src/MigrationTools/ServiceCollectionExtensions.cs @@ -29,7 +29,7 @@ public static OptionsBuilder AddMigrationToolsOptions(this I public static void AddMigrationToolServices(this IServiceCollection context, IConfiguration configuration, string configFile = "configuration.json") { // Infra - context.AddSingleton(); + context.AddSingleton(); context.AddSingleton(); From fe5b941d200989d9f0240785cff173232d9210ed Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:26:37 +0100 Subject: [PATCH 04/11] Update --- .../{OptionsConfiguration.cs => OptionsConfigurationBuilder.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/MigrationTools/Options/{OptionsConfiguration.cs => OptionsConfigurationBuilder.cs} (100%) diff --git a/src/MigrationTools/Options/OptionsConfiguration.cs b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs similarity index 100% rename from src/MigrationTools/Options/OptionsConfiguration.cs rename to src/MigrationTools/Options/OptionsConfigurationBuilder.cs From 8ed3b8bf14b65482b9002d4e3b94d864e295a97e Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:29:54 +0100 Subject: [PATCH 05/11] Rename ConfigurationBuilderCommand command --- .../{MigrationConfigCommand.cs => ConfigurationBuilderCommand.cs} | 0 ...gCommandSettings.cs => ConfigurationBuilderCommandSettings.cs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/MigrationTools.Host/Commands/{MigrationConfigCommand.cs => ConfigurationBuilderCommand.cs} (100%) rename src/MigrationTools.Host/Commands/{MigrationConfigCommandSettings.cs => ConfigurationBuilderCommandSettings.cs} (100%) diff --git a/src/MigrationTools.Host/Commands/MigrationConfigCommand.cs b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs similarity index 100% rename from src/MigrationTools.Host/Commands/MigrationConfigCommand.cs rename to src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs diff --git a/src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommandSettings.cs similarity index 100% rename from src/MigrationTools.Host/Commands/MigrationConfigCommandSettings.cs rename to src/MigrationTools.Host/Commands/ConfigurationBuilderCommandSettings.cs From b131df916ff6c64585900670b87345672009b562 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 11:43:40 +0100 Subject: [PATCH 06/11] Updarte --- docs/Reference/Generated/MigrationTools.xml | 12 ++--- .../Commands/InitMigrationCommand.cs | 35 ++----------- .../Commands/InitMigrationCommandSettings.cs | 18 +++---- .../Commands/UpgradeConfigCommand.cs | 2 - .../Options/OptionsConfigurationCompiler.cs | 1 + .../Options/OptionsConfigurationTemplates.cs | 52 +++++++++++++++++++ .../Options/OptionsConfigurationUpgrader.cs | 2 - 7 files changed, 68 insertions(+), 54 deletions(-) create mode 100644 src/MigrationTools/Options/OptionsConfigurationTemplates.cs diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 3515987af..79c333846 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -263,27 +263,27 @@ - => @"2fc40644" + => @"8ed3b8bf" - => @"2fc406443fd9372200d117e0dd124d16579a4638" + => @"8ed3b8bf14b65482b9002d4e3b94d864e295a97e" - => @"2024-09-04T11:25:39+01:00" + => @"2024-09-04T11:29:54+01:00" - => @"2" + => @"5" - => @"v16.0.0-Preview.9-2-g2fc40644" + => @"v16.0.0-Preview.9-5-g8ed3b8bf" @@ -318,7 +318,7 @@ - => @"2" + => @"5" diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs index 1b8c75810..f4fe12030 100644 --- a/src/MigrationTools.Host/Commands/InitMigrationCommand.cs +++ b/src/MigrationTools.Host/Commands/InitMigrationCommand.cs @@ -40,7 +40,7 @@ internal override async Task ExecuteInternalAsync(CommandContext context, I string configFile = settings.ConfigFile; if (string.IsNullOrEmpty(configFile)) { - configFile = $"configuration-{settings.Options.ToString()}.json"; + configFile = $"configuration-{settings.Template.ToString()}.json"; } _logger.LogInformation("ConfigFile: {configFile}", configFile); if (File.Exists(configFile)) @@ -57,39 +57,10 @@ internal override async Task ExecuteInternalAsync(CommandContext context, I } if (!File.Exists(configFile)) { - _logger.LogInformation("Populating config with {Options}", settings.Options.ToString()); + _logger.LogInformation("Populating config with {Options}", settings.Template.ToString()); OptionsConfigurationBuilder optionsBuilder = Services.GetService(); - - switch (settings.Options) - { - case OptionsMode.Reference: - optionsBuilder.AddAllOptions(); - break; - case OptionsMode.Basic: - optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); - optionsBuilder.AddOption("FieldMappingTool"); - optionsBuilder.AddOption("FieldLiteralMap"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Source"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Target"); - break; - case OptionsMode.WorkItemTracking: - optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); - optionsBuilder.AddOption("FieldMappingTool"); - optionsBuilder.AddOption("FieldLiteralMap"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Source"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Target"); - break; - case OptionsMode.PipelineProcessor: - optionsBuilder.AddOption("AzureDevOpsPipelineProcessor"); - optionsBuilder.AddOption("AzureDevOpsEndpoint", "Source"); - optionsBuilder.AddOption("AzureDevOpsEndpoint", "Target"); - break; - default: - optionsBuilder.AddAllOptions(); - break; - } - + optionsBuilder.ApplyTemplate(settings.Template); string json = optionsBuilder.Build(); File.WriteAllText(configFile, json); diff --git a/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs index b7a98a092..8a157c1a2 100644 --- a/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs +++ b/src/MigrationTools.Host/Commands/InitMigrationCommandSettings.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using MigrationTools.Options; using Spectre.Console.Cli; namespace MigrationTools.Host.Commands @@ -6,22 +7,15 @@ namespace MigrationTools.Host.Commands internal class InitMigrationCommandSettings : CommandSettingsBase { [Description("What type of config do you want to output? WorkItemTracking is the default.")] - [CommandOption("--outputMode|--options")] - [DefaultValue(OptionsMode.WorkItemTracking)] - public OptionsMode Options { get; set; } + [CommandOption("--template|-t|--outputMode|--options")] + [DefaultValue(OptionsConfigurationTemplate.WorkItemTracking)] + public OptionsConfigurationTemplate Template { get; set; } - [Description("Add to overwirte the existing file.")] - [CommandOption("--overwrite")] + [Description("Add to overwrite the existing file.")] + [CommandOption("--overwrite|-o")] [DefaultValue(false)] public bool Overwrite { get; set; } } - public enum OptionsMode - { - Reference = 0, - WorkItemTracking = 1, - Basic = 3, - PipelineProcessor = 5 - } } \ No newline at end of file diff --git a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs index 07a6cfbf9..9818fe5c5 100644 --- a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs +++ b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs @@ -57,9 +57,7 @@ internal override async Task ExecuteInternalAsync(CommandContext context, U OptionsConfigurationBuilder optionsBuilder = Services.GetRequiredService(); OptionsConfigurationUpgrader optionsUpgrader = Services.GetRequiredService(); - optionsUpgrader.UpgradeConfiguration(optionsBuilder); - string json = optionsBuilder.Build(); configFile = AddSuffixToFileName(configFile, "-upgraded"); File.WriteAllText(configFile, json); diff --git a/src/MigrationTools/Options/OptionsConfigurationCompiler.cs b/src/MigrationTools/Options/OptionsConfigurationCompiler.cs index e6ea0aed5..ecb110b3f 100644 --- a/src/MigrationTools/Options/OptionsConfigurationCompiler.cs +++ b/src/MigrationTools/Options/OptionsConfigurationCompiler.cs @@ -11,6 +11,7 @@ namespace MigrationTools.Options public static class OptionsConfigurationCompiler { + public static IOptions LoadConfiguration(string filePath, IOptions options, bool isCollection = false) { var optionsConfig = options.ConfigurationMetadata; diff --git a/src/MigrationTools/Options/OptionsConfigurationTemplates.cs b/src/MigrationTools/Options/OptionsConfigurationTemplates.cs new file mode 100644 index 000000000..e00fa9089 --- /dev/null +++ b/src/MigrationTools/Options/OptionsConfigurationTemplates.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MigrationTools.Options +{ + public enum OptionsConfigurationTemplate + { + Reference = 0, + WorkItemTracking = 1, + Basic = 3, + PipelineProcessor = 5 + } + + public static class OptionsConfigurationTemplateExtensions + { + + public static OptionsConfigurationBuilder ApplyTemplate(this OptionsConfigurationBuilder optionsBuilder, OptionsConfigurationTemplate template) + { + switch (template) + { + case OptionsConfigurationTemplate.Reference: + optionsBuilder.AddAllOptions(); + break; + case OptionsConfigurationTemplate.Basic: + optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); + optionsBuilder.AddOption("FieldMappingTool"); + optionsBuilder.AddOption("FieldLiteralMap"); + optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Source"); + optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Target"); + break; + case OptionsConfigurationTemplate.WorkItemTracking: + optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); + optionsBuilder.AddOption("FieldMappingTool"); + optionsBuilder.AddOption("FieldLiteralMap"); + optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Source"); + optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Target"); + break; + case OptionsConfigurationTemplate.PipelineProcessor: + optionsBuilder.AddOption("AzureDevOpsPipelineProcessor"); + optionsBuilder.AddOption("AzureDevOpsEndpoint", "Source"); + optionsBuilder.AddOption("AzureDevOpsEndpoint", "Target"); + break; + default: + optionsBuilder.AddAllOptions(); + break; + } + return optionsBuilder; + } + + } +} diff --git a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs index f2a10a8d8..94e413af7 100644 --- a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs +++ b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs @@ -59,8 +59,6 @@ public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuil { using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("OptionsConfigurationUpgrader::UpgradeConfiguration")) { - - if (optionsBuilder == null) { optionsBuilder = this.Services.GetService(); From c18548acc45bff8d0ac85edde253ecda425d981d Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 12:09:28 +0100 Subject: [PATCH 07/11] Update to get started --- docs/Reference/Generated/MigrationTools.xml | 12 +++---- .../Commands/ConfigurationBuilderCommand.cs | 36 +++++++++++++++---- src/MigrationTools.Host/MigrationToolHost.cs | 13 +++---- .../Options/OptionsConfigurationBuilder.cs | 8 +++++ 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 79c333846..a110c5c47 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -263,27 +263,27 @@ - => @"8ed3b8bf" + => @"b131df91" - => @"8ed3b8bf14b65482b9002d4e3b94d864e295a97e" + => @"b131df916ff6c64585900670b87345672009b562" - => @"2024-09-04T11:29:54+01:00" + => @"2024-09-04T11:43:40+01:00" - => @"5" + => @"6" - => @"v16.0.0-Preview.9-5-g8ed3b8bf" + => @"v16.0.0-Preview.9-6-gb131df91" @@ -318,7 +318,7 @@ - => @"5" + => @"6" diff --git a/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs index 46b2fdfa5..7d41bdb94 100644 --- a/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs +++ b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs @@ -3,9 +3,11 @@ using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Services.Common; +using MigrationTools.Options; using Newtonsoft.Json.Linq; using Spectre.Console; using Spectre.Console.Cli; @@ -46,14 +48,12 @@ public override async Task ExecuteAsync(CommandContext context, Configurati _logger.LogInformation("ConfigFile: {configFile}", configFile); // Load configuration - var configuration = new ConfigurationBuilder() - .SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile(configFile, optional: true, reloadOnChange: true) - .Build(); + OptionsConfigurationBuilder optionsBuilder = _services.GetRequiredService(); + OptionsConfigurationUpgrader optionsUpgrader = _services.GetRequiredService(); + optionsUpgrader.UpgradeConfiguration(optionsBuilder); - var json = File.ReadAllText(configFile); - var jsonObj = JObject.Parse(json); + // Dispay Current Options + DisplayCurrentOptions(); var configurationEditorOptions = new[] @@ -109,6 +109,28 @@ public override async Task ExecuteAsync(CommandContext context, Configurati return _exitCode; } + private void DisplayCurrentOptions() + { + var layout = new Layout("Root") + .SplitColumns( + new Layout("Left"), + new Layout("Right") + .SplitRows( + new Layout("Top"), + new Layout("Bottom"))); + + // Update the left column + layout["Left"].Update( + new Panel( + Align.Center( + new Markup("Hello [blue]World![/]"), + VerticalAlignment.Middle)) + .Expand()); + + // Render the layout + AnsiConsole.Write(layout); + } + private void SelectTemplateToApply() { Console.Clear(); diff --git a/src/MigrationTools.Host/MigrationToolHost.cs b/src/MigrationTools.Host/MigrationToolHost.cs index 20707f1fb..89c8c8aca 100644 --- a/src/MigrationTools.Host/MigrationToolHost.cs +++ b/src/MigrationTools.Host/MigrationToolHost.cs @@ -29,6 +29,7 @@ using Azure.Monitor.OpenTelemetry.Exporter; using System.Diagnostics; using OpenTelemetry.Logs; +using System.Net.NetworkInformation; namespace MigrationTools.Host { @@ -119,15 +120,15 @@ public static IHostBuilder CreateDefaultBuilder(string[] args, Action("init") - .WithDescription("Creates an default configuration file") - .WithExample("init -options Basic"); + .WithDescription($"Creates an default configuration file or applies templates! Available templates are `{string.Join("`, `", Enum.GetNames(typeof(OptionsConfigurationTemplate)))}`. With `Reference` loading all options with their defaults.") + .WithExample("init -template Basic"); config.AddCommand("upgrade") - .WithDescription("Atempts to upgrade your config from the old version to the new one. For each object we will load the defaults, then apply your config. This will only bring accross valid settings. This is 'best effort' and you will need to check all the values as we have changed a lot!") + .WithDescription("Attempts to upgrade your config from the old version to the new one. For each option we will load the defaults, then apply your config. This will only bring across valid settings. This is 'best effort' and you will need to check all the values as we have changed a lot! Also note that it will automatically load any defaults in Environment variables which may require you to remove secrets from the output file.") .WithExample("upgrade -config \"configuration.json\""); - //config.AddCommand("builder") - // .WithDescription("Creates or edits a configuration file") - // .WithExample("config -config \"configuration.json\""); + config.AddCommand("builder") + .WithDescription("Creates or edits a configuration file") + .WithExample("builder -config \"configuration.json\"").IsHidden(); extraCommands?.Invoke(config); config.PropagateExceptions(); diff --git a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs index baf16fd45..ff630201f 100644 --- a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs +++ b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs @@ -17,6 +17,14 @@ namespace MigrationTools.Options { + + public class OptionItem + { + string typeName { get; set; } + string key { get; set; } + IOptions Option { get; set; } + } + public class OptionsConfigurationBuilder { readonly ILogger logger; From 03976a5dcf2c350da1946e2c34a32432eda34e05 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 13:13:38 +0100 Subject: [PATCH 08/11] Upd datearouny --- configuration-classic.json | 4 +- configuration-classic2.json | 4 +- configuration.json | 4 +- docs/Reference/Generated/MigrationTools.xml | 12 +- docs/Reference/index.md | 4 +- ...ence.endpoints.tfsteamprojectendpoint.yaml | 4 +- ...ocessors.tfsworkitembulkeditprocessor.yaml | 2 +- docs/_data/releases-grouped-minor.json | 12 +- docs/_data/releases.json | 12 +- .../sampleConfig/configration-demo-v15.0.json | 4 +- .../configuration-WorkItemTracking.json | 4 +- .../sampleConfig/configuration-full.json | 4 +- .../_includes/sampleConfig/configuration.json | 2 +- .../creating-iteration-and-area-maps.md | 12 +- .../_howto/migrating-plans-and-suits.md | 12 +- docs/collections/_howto/user-mappings.md | 4 +- ...erence.endpoints.tfsteamprojectendpoint.md | 4 +- ...processors.tfsworkitembulkeditprocessor.md | 2 +- docs/getstarted/index.md | 2 +- docs/setup/ReflectedWorkItemId.md | 2 +- .../Endpoints/TfsWorkItemEndPointTests.cs | 14 +- .../Processors/TfsProcessorTests.cs | 18 +- .../ServiceProviderHelper.cs | 22 +- .../Clients/TfsWorkItemMigrationClient.cs | 10 +- .../TfsTeamProjectEndPointOptions.cs | 21 +- .../Endpoints/TfsEndpoint.cs | 36 +-- .../Endpoints/TfsEndpointOptions.cs | 39 ++- ...TfsTestPlansAndSuitesMigrationProcessor.cs | 10 +- .../TfsWorkItemBulkEditProcessorOptions.cs | 2 +- .../TfsWorkItemMigrationProcessor.cs | 12 +- .../TfsWorkItemOverwriteProcessorOptions.cs | 2 +- .../Tools/TfsValidateRequiredFieldTool.cs | 6 +- src/MigrationTools.Samples/configuration.json | 4 +- .../demo-mapping-scrum2Agile.json | 2 +- .../demo-migration-reset.json | 2 +- .../demo-migration.json | 2 +- .../Options/OptionsConfigurationBuilder.cs | 251 ++++++++++++++---- 37 files changed, 347 insertions(+), 216 deletions(-) diff --git a/configuration-classic.json b/configuration-classic.json index 15ea92298..fe69790ef 100644 --- a/configuration-classic.json +++ b/configuration-classic.json @@ -4,7 +4,7 @@ "$type": "TfsTeamProjectEndpointOptions", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "AccessToken", "PersonalAccessToken": "", @@ -19,7 +19,7 @@ "$type": "TfsTeamProjectEndpointOptions", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "AccessToken", "PersonalAccessToken": "", diff --git a/configuration-classic2.json b/configuration-classic2.json index 17429db36..695bce1a3 100644 --- a/configuration-classic2.json +++ b/configuration-classic2.json @@ -150,7 +150,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AuthenticationMode": "Prompt", "AllowCrossProjectLinking": false, "PersonalAccessToken": "qosss7crwz3vie4fupzpaafjndoy6g6ulgkzhoxtmjgicv2lqjyq", @@ -163,7 +163,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTarget1", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AuthenticationMode": "Prompt", "AllowCrossProjectLinking": false, "PersonalAccessToken": "qosss7crwz3vie4fupzpaafjndoy6g6ulgkzhoxtmjgicv2lqjyq", diff --git a/configuration.json b/configuration.json index f0a8c5348..107f9c62d 100644 --- a/configuration.json +++ b/configuration.json @@ -10,7 +10,7 @@ "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", "AllowCrossProjectLinking": false, - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "Authentication": { "AuthenticationMode": "AccessToken", "AccessToken": "", @@ -39,7 +39,7 @@ "Domain": "" } }, - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "LanguageMaps": { "AreaPath": "Area", diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index a110c5c47..56d79f75a 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -263,27 +263,27 @@ - => @"b131df91" + => @"c18548ac" - => @"b131df916ff6c64585900670b87345672009b562" + => @"c18548acc45bff8d0ac85edde253ecda425d981d" - => @"2024-09-04T11:43:40+01:00" + => @"2024-09-04T12:09:28+01:00" - => @"6" + => @"7" - => @"v16.0.0-Preview.9-6-gb131df91" + => @"v16.0.0-Preview.9-7-gc18548ac" @@ -318,7 +318,7 @@ - => @"6" + => @"7" diff --git a/docs/Reference/index.md b/docs/Reference/index.md index b7d16715e..edc354473 100644 --- a/docs/Reference/index.md +++ b/docs/Reference/index.md @@ -80,7 +80,7 @@ The global configuration created by the `init` command look something like this: "Domain": "" } }, - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "LanguageMaps": { "AreaPath": "Area", @@ -227,7 +227,7 @@ Both the `Source` and `Target` entries hold the collection URL and the Team Proj For multi Language support you can add the name used in the source and target for both 'Area' and 'Iteration'. This allows a migration from one language version of TFS / Azure DevOps to another. -#### ReflectedWorkItemIDFieldName +#### ReflectedWorkItemIdField This is the field that will be used to store the state for the migration . See [Server Configuration](server-configuration.md) diff --git a/docs/_data/reference.endpoints.tfsteamprojectendpoint.yaml b/docs/_data/reference.endpoints.tfsteamprojectendpoint.yaml index 7ad9db615..6e91ba38e 100644 --- a/docs/_data/reference.endpoints.tfsteamprojectendpoint.yaml +++ b/docs/_data/reference.endpoints.tfsteamprojectendpoint.yaml @@ -80,7 +80,7 @@ configurationSamples: }, "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad" }, - "ReflectedWorkItemIDFieldName": null, + "ReflectedWorkItemIdField": null, "AllowCrossProjectLinking": false, "LanguageMaps": { "AreaPath": "Area", @@ -118,7 +118,7 @@ options: type: String description: missng XML code comments defaultValue: missng XML code comments -- parameterName: ReflectedWorkItemIDFieldName +- parameterName: ReflectedWorkItemIdField type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/_data/reference.processors.tfsworkitembulkeditprocessor.yaml b/docs/_data/reference.processors.tfsworkitembulkeditprocessor.yaml index a0c7252b4..40d7709b7 100644 --- a/docs/_data/reference.processors.tfsworkitembulkeditprocessor.yaml +++ b/docs/_data/reference.processors.tfsworkitembulkeditprocessor.yaml @@ -16,7 +16,7 @@ configurationSamples: "$type": "TfsWorkItemBulkEditProcessorOptions", "Enabled": false, "WhatIf": false, - "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdField] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, diff --git a/docs/_data/releases-grouped-minor.json b/docs/_data/releases-grouped-minor.json index 67586c2f6..81d28a0f4 100644 --- a/docs/_data/releases-grouped-minor.json +++ b/docs/_data/releases-grouped-minor.json @@ -195,7 +195,7 @@ "publishedAt": "2020-09-22T13:07:25Z", "tagName": "v10.2.4", "version": "10.2.4", - "description": "The recent changes include an update to the `configuration.json` file, where the `ReflectedWorkItemIDFieldName` has been modified to `nkdScrum.ReflectedWorkItemId`, and various code files have been refactored to replace the `TelemetryClient` with a new `ITelemetryLogger` interface, enhancing telemetry logging capabilities throughout the application. Additionally, several methods now utilize this new telemetry interface for tracking events, exceptions, and dependencies, which may improve the overall logging and monitoring experience for users." + "description": "The recent changes include an update to the `configuration.json` file, where the `ReflectedWorkItemIdField` has been modified to `nkdScrum.ReflectedWorkItemId`, and various code files have been refactored to replace the `TelemetryClient` with a new `ITelemetryLogger` interface, enhancing telemetry logging capabilities throughout the application. Additionally, several methods now utilize this new telemetry interface for tracking events, exceptions, and dependencies, which may improve the overall logging and monitoring experience for users." }, { "name": "v10.2.3", @@ -815,7 +815,7 @@ "publishedAt": "2020-10-09T08:44:35Z", "tagName": "v11.5.18", "version": "11.5.18", - "description": "The recent changes include the introduction of a new configuration structure, replacing the previous `TeamProjectConfig` with an interface `IMigrationClientConfig`, which allows for more flexible configuration management. The configuration now includes an `ObjectType` field, and various properties such as `Collection`, `Project`, and `ReflectedWorkItemIDFieldName` have been updated to utilize this new interface. Additionally, several methods and properties across the codebase have been modified to reference the new configuration structure, ensuring that the migration tools can now handle configurations more dynamically and robustly." + "description": "The recent changes include the introduction of a new configuration structure, replacing the previous `TeamProjectConfig` with an interface `IMigrationClientConfig`, which allows for more flexible configuration management. The configuration now includes an `ObjectType` field, and various properties such as `Collection`, `Project`, and `ReflectedWorkItemIdField` have been updated to utilize this new interface. Additionally, several methods and properties across the codebase have been modified to reference the new configuration structure, ensuring that the migration tools can now handle configurations more dynamically and robustly." }, { "name": "v11.5.17", @@ -1302,7 +1302,7 @@ "publishedAt": "2020-11-14T22:21:30Z", "tagName": "v11.7.5", "version": "11.7.5", - "description": "The recent changes in the `WorkItemMigrationContext.cs` file introduce a validation check to ensure that the target work item contains the specified `ReflectedWorkItemIDFieldName` field; if the field is missing, an error is logged and an exception is thrown, preventing the migration process from proceeding without the necessary configuration." + "description": "The recent changes in the `WorkItemMigrationContext.cs` file introduce a validation check to ensure that the target work item contains the specified `ReflectedWorkItemIdField` field; if the field is missing, an error is logged and an exception is thrown, preventing the migration process from proceeding without the necessary configuration." }, { "name": "v11.7.4", @@ -3392,7 +3392,7 @@ "publishedAt": "2019-09-18T08:46:33Z", "tagName": "7.5.74", "version": "7.5.74", - "description": "The recent changes include an update to the `next-version` in the `GitVersion.yml` file from 7.5.0 to 8.0.0, the addition of a new configuration option `ReplayRevisions` in the `WorkItemMigrationConfig` class, and modifications to the documentation to clarify the configuration requirements for the `ReflectedWorkItemIDFieldName` in both source and target configurations, enhancing user guidance on customization and setup." + "description": "The recent changes include an update to the `next-version` in the `GitVersion.yml` file from 7.5.0 to 8.0.0, the addition of a new configuration option `ReplayRevisions` in the `WorkItemMigrationConfig` class, and modifications to the documentation to clarify the configuration requirements for the `ReflectedWorkItemIdField` in both source and target configurations, enhancing user guidance on customization and setup." }, { "name": "7.5.73", @@ -3406,7 +3406,7 @@ "publishedAt": "2019-09-17T09:36:45Z", "tagName": "7.5.72", "version": "7.5.72", - "description": "This release includes several updates to the configuration options for the Azure DevOps Migration Tools, notably consolidating the handling of the `ReflectedWorkItemIDFieldName` within the `TeamProjectConfig` class, which now directly includes this field. Additionally, various references to project names have been updated to utilize the new configuration structure, ensuring consistency across the codebase. The documentation has also been corrected to reflect the proper spelling of \"recommended\" in the installation instructions." + "description": "This release includes several updates to the configuration options for the Azure DevOps Migration Tools, notably consolidating the handling of the `ReflectedWorkItemIdField` within the `TeamProjectConfig` class, which now directly includes this field. Additionally, various references to project names have been updated to utilize the new configuration structure, ensuring consistency across the codebase. The documentation has also been corrected to reflect the proper spelling of \"recommended\" in the installation instructions." }, { "name": "7.5.71", @@ -3728,7 +3728,7 @@ "publishedAt": "2018-02-21T18:12:24Z", "tagName": "7.5.26", "version": "7.5.26", - "description": "This release introduces a new configuration option, `SourceReflectedWorkItemIDFieldName`, allowing users to specify a different field name for reflected work item IDs in the source project, enhancing flexibility in migration setups. Additionally, several methods have been updated to handle string comparisons and file operations more robustly, ensuring improved reliability when processing work items and attachments." + "description": "This release introduces a new configuration option, `SourceReflectedWorkItemIdField`, allowing users to specify a different field name for reflected work item IDs in the source project, enhancing flexibility in migration setups. Additionally, several methods have been updated to handle string comparisons and file operations more robustly, ensuring improved reliability when processing work items and attachments." }, { "name": "7.5.25", diff --git a/docs/_data/releases.json b/docs/_data/releases.json index 6ce3b2b14..fa8dbf6aa 100644 --- a/docs/_data/releases.json +++ b/docs/_data/releases.json @@ -1705,7 +1705,7 @@ "publishedAt": "2020-11-14T22:21:30Z", "tagName": "v11.7.5", "version": "11.7.5", - "description": "The recent changes in the `WorkItemMigrationContext.cs` file introduce a validation check to ensure that the target work item contains the specified `ReflectedWorkItemIDFieldName` field; if the field is missing, an error is logged and an exception is thrown, preventing the migration process from proceeding without the necessary configuration." + "description": "The recent changes in the `WorkItemMigrationContext.cs` file introduce a validation check to ensure that the target work item contains the specified `ReflectedWorkItemIdField` field; if the field is missing, an error is logged and an exception is thrown, preventing the migration process from proceeding without the necessary configuration." }, { "name": "v11.7.4", @@ -2069,7 +2069,7 @@ "publishedAt": "2020-10-09T08:44:35Z", "tagName": "v11.5.18", "version": "11.5.18", - "description": "The recent changes include the introduction of a new configuration structure, replacing the previous `TeamProjectConfig` with an interface `IMigrationClientConfig`, which allows for more flexible configuration management. The configuration now includes an `ObjectType` field, and various properties such as `Collection`, `Project`, and `ReflectedWorkItemIDFieldName` have been updated to utilize this new interface. Additionally, several methods and properties across the codebase have been modified to reference the new configuration structure, ensuring that the migration tools can now handle configurations more dynamically and robustly." + "description": "The recent changes include the introduction of a new configuration structure, replacing the previous `TeamProjectConfig` with an interface `IMigrationClientConfig`, which allows for more flexible configuration management. The configuration now includes an `ObjectType` field, and various properties such as `Collection`, `Project`, and `ReflectedWorkItemIdField` have been updated to utilize this new interface. Additionally, several methods and properties across the codebase have been modified to reference the new configuration structure, ensuring that the migration tools can now handle configurations more dynamically and robustly." }, { "name": "v11.5.17", @@ -2391,7 +2391,7 @@ "publishedAt": "2020-09-22T13:07:25Z", "tagName": "v10.2.4", "version": "10.2.4", - "description": "The recent changes include an update to the `configuration.json` file, where the `ReflectedWorkItemIDFieldName` has been modified to `nkdScrum.ReflectedWorkItemId`, and various code files have been refactored to replace the `TelemetryClient` with a new `ITelemetryLogger` interface, enhancing telemetry logging capabilities throughout the application. Additionally, several methods now utilize this new telemetry interface for tracking events, exceptions, and dependencies, which may improve the overall logging and monitoring experience for users." + "description": "The recent changes include an update to the `configuration.json` file, where the `ReflectedWorkItemIdField` has been modified to `nkdScrum.ReflectedWorkItemId`, and various code files have been refactored to replace the `TelemetryClient` with a new `ITelemetryLogger` interface, enhancing telemetry logging capabilities throughout the application. Additionally, several methods now utilize this new telemetry interface for tracking events, exceptions, and dependencies, which may improve the overall logging and monitoring experience for users." }, { "name": "v10.2.3", @@ -3147,7 +3147,7 @@ "publishedAt": "2019-09-18T08:46:33Z", "tagName": "7.5.74", "version": "7.5.74", - "description": "The recent changes include an update to the `next-version` in the `GitVersion.yml` file from 7.5.0 to 8.0.0, the addition of a new configuration option `ReplayRevisions` in the `WorkItemMigrationConfig` class, and modifications to the documentation to clarify the configuration requirements for the `ReflectedWorkItemIDFieldName` in both source and target configurations, enhancing user guidance on customization and setup." + "description": "The recent changes include an update to the `next-version` in the `GitVersion.yml` file from 7.5.0 to 8.0.0, the addition of a new configuration option `ReplayRevisions` in the `WorkItemMigrationConfig` class, and modifications to the documentation to clarify the configuration requirements for the `ReflectedWorkItemIdField` in both source and target configurations, enhancing user guidance on customization and setup." }, { "name": "7.5.73", @@ -3161,7 +3161,7 @@ "publishedAt": "2019-09-17T09:36:45Z", "tagName": "7.5.72", "version": "7.5.72", - "description": "This release includes several updates to the configuration options for the Azure DevOps Migration Tools, notably consolidating the handling of the `ReflectedWorkItemIDFieldName` within the `TeamProjectConfig` class, which now directly includes this field. Additionally, various references to project names have been updated to utilize the new configuration structure, ensuring consistency across the codebase. The documentation has also been corrected to reflect the proper spelling of \"recommended\" in the installation instructions." + "description": "This release includes several updates to the configuration options for the Azure DevOps Migration Tools, notably consolidating the handling of the `ReflectedWorkItemIdField` within the `TeamProjectConfig` class, which now directly includes this field. Additionally, various references to project names have been updated to utilize the new configuration structure, ensuring consistency across the codebase. The documentation has also been corrected to reflect the proper spelling of \"recommended\" in the installation instructions." }, { "name": "7.5.71", @@ -3483,7 +3483,7 @@ "publishedAt": "2018-02-21T18:12:24Z", "tagName": "7.5.26", "version": "7.5.26", - "description": "This release introduces a new configuration option, `SourceReflectedWorkItemIDFieldName`, allowing users to specify a different field name for reflected work item IDs in the source project, enhancing flexibility in migration setups. Additionally, several methods have been updated to handle string comparisons and file operations more robustly, ensuring improved reliability when processing work items and attachments." + "description": "This release introduces a new configuration option, `SourceReflectedWorkItemIdField`, allowing users to specify a different field name for reflected work item IDs in the source project, enhancing flexibility in migration setups. Additionally, several methods have been updated to handle string comparisons and file operations more robustly, ensuring improved reliability when processing work items and attachments." }, { "name": "7.5.25", diff --git a/docs/_includes/sampleConfig/configration-demo-v15.0.json b/docs/_includes/sampleConfig/configration-demo-v15.0.json index bb59fdf69..f7e920697 100644 --- a/docs/_includes/sampleConfig/configration-demo-v15.0.json +++ b/docs/_includes/sampleConfig/configration-demo-v15.0.json @@ -4,7 +4,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", @@ -18,7 +18,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "njp3kcec4nbev63fmbepvdpn35drawmonk5qf5yqsw77dgfwnjda", diff --git a/docs/_includes/sampleConfig/configuration-WorkItemTracking.json b/docs/_includes/sampleConfig/configuration-WorkItemTracking.json index ffdcbbfb4..34b1508e2 100644 --- a/docs/_includes/sampleConfig/configuration-WorkItemTracking.json +++ b/docs/_includes/sampleConfig/configuration-WorkItemTracking.json @@ -4,7 +4,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "myProjectName", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", @@ -18,7 +18,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "myProjectName", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", diff --git a/docs/_includes/sampleConfig/configuration-full.json b/docs/_includes/sampleConfig/configuration-full.json index 512cc7f88..fcb784284 100644 --- a/docs/_includes/sampleConfig/configuration-full.json +++ b/docs/_includes/sampleConfig/configuration-full.json @@ -4,7 +4,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "myProjectName", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", @@ -18,7 +18,7 @@ "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "myProjectName", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", diff --git a/docs/_includes/sampleConfig/configuration.json b/docs/_includes/sampleConfig/configuration.json index 8dbb6ea78..71969bc50 100644 --- a/docs/_includes/sampleConfig/configuration.json +++ b/docs/_includes/sampleConfig/configuration.json @@ -22,7 +22,7 @@ "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", "TfsVersion": "AzureDevOps", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "AccessToken", "PersonalAccessToken": "", diff --git a/docs/collections/_howto/creating-iteration-and-area-maps.md b/docs/collections/_howto/creating-iteration-and-area-maps.md index be23c8735..fd55cff07 100644 --- a/docs/collections/_howto/creating-iteration-and-area-maps.md +++ b/docs/collections/_howto/creating-iteration-and-area-maps.md @@ -19,7 +19,7 @@ This will migrate all of the work items, while also populating `IntegrationBuild The important bits: -- Target ReflectedWorkItemIDFieldName is your main Custom field. +- Target ReflectedWorkItemIdField is your main Custom field. - Field map copies the `ReflectedWorkItemId` to `Microsoft.VSTS.Build.IntegrationBuild` - Exclude all test based work items from the query @@ -43,7 +43,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -86,7 +86,7 @@ This will migrate Test Cases while fixing the links to the Shared bits that can' The important bits: -- Target ReflectedWorkItemIDFieldName is a common field that is available on the non-customisable work items +- Target ReflectedWorkItemIdField is a common field that is available on the non-customisable work items - Field Map copies `Microsoft.VSTS.Build.IntegrationBuild` to `ReflectedWorkItemId` for Test Cases only - The query includes only the Test items that we can migrate as work items (No Suits or Plans) @@ -109,7 +109,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Microsoft.VSTS.Build.IntegrationBuild", + "ReflectedWorkItemIdField": "Microsoft.VSTS.Build.IntegrationBuild", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -171,7 +171,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -231,7 +231,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", diff --git a/docs/collections/_howto/migrating-plans-and-suits.md b/docs/collections/_howto/migrating-plans-and-suits.md index beabfa785..9ad110e73 100644 --- a/docs/collections/_howto/migrating-plans-and-suits.md +++ b/docs/collections/_howto/migrating-plans-and-suits.md @@ -20,7 +20,7 @@ This will migrate all of the work items, while also populating `IntegrationBuild The important bits: -- Target ReflectedWorkItemIDFieldName is your main Custom field. +- Target ReflectedWorkItemIdField is your main Custom field. - Field map copies the `ReflectedWorkItemId` to `Microsoft.VSTS.Build.IntegrationBuild` - Exclude all test based work items from the query @@ -44,7 +44,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -87,7 +87,7 @@ This will migrate Test Cases while fixing the links to the Shared bits that can' The important bits: -- Target ReflectedWorkItemIDFieldName is a common field that is available on the non-customisable work items +- Target ReflectedWorkItemIdField is a common field that is available on the non-customisable work items - Field Map copies `Microsoft.VSTS.Build.IntegrationBuild` to `ReflectedWorkItemId` for Test Cases only - The query includes only the Test items that we can migrate as work items (No Suits or Plans) @@ -110,7 +110,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Microsoft.VSTS.Build.IntegrationBuild", + "ReflectedWorkItemIdField": "Microsoft.VSTS.Build.IntegrationBuild", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -172,7 +172,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", @@ -232,7 +232,7 @@ The important bits: "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "XXXXXXXXXXXXXX", diff --git a/docs/collections/_howto/user-mappings.md b/docs/collections/_howto/user-mappings.md index 0ae206c4c..0a8a99f19 100644 --- a/docs/collections/_howto/user-mappings.md +++ b/docs/collections/_howto/user-mappings.md @@ -57,7 +57,7 @@ There was a request to have the ability to map users to try and maintain integri "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility/", "Project": "AzureDevOps-Tools", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", @@ -71,7 +71,7 @@ There was a request to have the ability to map users to try and maintain integri "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTest5", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "AuthenticationMode": "Prompt", "PersonalAccessToken": "", diff --git a/docs/collections/_reference/reference.endpoints.tfsteamprojectendpoint.md b/docs/collections/_reference/reference.endpoints.tfsteamprojectendpoint.md index 1e735ef02..7b8896690 100644 --- a/docs/collections/_reference/reference.endpoints.tfsteamprojectendpoint.md +++ b/docs/collections/_reference/reference.endpoints.tfsteamprojectendpoint.md @@ -81,7 +81,7 @@ configurationSamples: }, "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad" }, - "ReflectedWorkItemIDFieldName": null, + "ReflectedWorkItemIdField": null, "AllowCrossProjectLinking": false, "LanguageMaps": { "AreaPath": "Area", @@ -119,7 +119,7 @@ options: type: String description: missng XML code comments defaultValue: missng XML code comments -- parameterName: ReflectedWorkItemIDFieldName +- parameterName: ReflectedWorkItemIdField type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/collections/_reference/reference.processors.tfsworkitembulkeditprocessor.md b/docs/collections/_reference/reference.processors.tfsworkitembulkeditprocessor.md index 0bb30ebcb..5bdb4f5ec 100644 --- a/docs/collections/_reference/reference.processors.tfsworkitembulkeditprocessor.md +++ b/docs/collections/_reference/reference.processors.tfsworkitembulkeditprocessor.md @@ -17,7 +17,7 @@ configurationSamples: "$type": "TfsWorkItemBulkEditProcessorOptions", "Enabled": false, "WhatIf": false, - "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdField] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, diff --git a/docs/getstarted/index.md b/docs/getstarted/index.md index 0425ead0a..6a4349747 100644 --- a/docs/getstarted/index.md +++ b/docs/getstarted/index.md @@ -56,7 +56,7 @@ The default [TfsWorkItemMigrationProcesor](_reference/reference.processors.tfswo for the `PersonalAccessToken` attribute, or set the `PersonalAccessTokenVariableName` to the name of an environment variable containing your PAT. -4. Adjust the value of the `ReflectedWorkItemIDFieldName` attribute (field name of the migration tracking field) for Source and Target +4. Adjust the value of the `ReflectedWorkItemIdField` attribute (field name of the migration tracking field) for Source and Target For example: `TfsMigrationTool.ReflectedWorkItemId` for TFS, `ReflectedWorkItemId` for VSTS, or `Custom.ReflectedWorkItemId` for Azure DevOps diff --git a/docs/setup/ReflectedWorkItemId.md b/docs/setup/ReflectedWorkItemId.md index 3e900b280..39a48d0d9 100644 --- a/docs/setup/ReflectedWorkItemId.md +++ b/docs/setup/ReflectedWorkItemId.md @@ -25,7 +25,7 @@ In your configuration file under `MigrationTools:Endpoints` there willbe both a "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", "AllowCrossProjectLinking": false, - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "Authentication": { "AuthenticationMode": "AccessToken", "AccessToken": "alakjhsaggdsad67869asdjksafksldjhgsjkdghsdkfhskdf", diff --git a/src/MigrationTools.Clients.TfsObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs b/src/MigrationTools.Clients.TfsObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs index fdb7240e0..3e5bb3100 100644 --- a/src/MigrationTools.Clients.TfsObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs +++ b/src/MigrationTools.Clients.TfsObjectModel.Tests/Endpoints/TfsWorkItemEndPointTests.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.Configuration; using System.IO; using System.Text; +using MigrationTools.Endpoints.Infrastructure; namespace MigrationTools.Endpoints.Tests { @@ -80,10 +81,13 @@ protected TfsWorkItemEndpoint GetTfsWorkItemEndPoint(string key = "Source", TfsW { IOptions wrappedOptions = Microsoft.Extensions.Options.Options.Create(new TfsWorkItemEndpointOptions() { - Organisation = options != null? options.Organisation : "https://dev.azure.com/nkdagility-preview/", + Collection = options != null? options.Collection : new Uri("https://dev.azure.com/nkdagility-preview/"), Project = options != null ? options.Project : "migrationSource1", - AuthenticationMode = options != null ? options.AuthenticationMode : AuthenticationMode.AccessToken, - AccessToken = options != null ? options.AccessToken : TestingConstants.AccessToken, + Authentication = new TfsAuthenticationOptions() + { + AccessToken = options != null ? options.Authentication.AccessToken : TestingConstants.AccessToken, + AuthenticationMode = options != null ? options.Authentication.AuthenticationMode : AuthenticationMode.AccessToken + }, Query = options != null ? options.Query : new Options.QueryOptions() { Query = "SELECT [System.Id], [System.Tags] " + @@ -135,7 +139,7 @@ private static IConfigurationBuilder GetSourceTargetBasicConfig() ""Collection"": ""https://dev.azure.com/nkdagility-preview/"", ""Project"": ""migrationSource1"", ""AllowCrossProjectLinking"": false, - ""ReflectedWorkItemIDFieldName"": ""Custom.ReflectedWorkItemId"", + ""ReflectedWorkItemIdField"": ""Custom.ReflectedWorkItemId"", ""Authentication"": { ""AuthenticationMode"": ""AccessToken"", ""AccessToken"": ""123456"", @@ -164,7 +168,7 @@ private static IConfigurationBuilder GetSourceTargetBasicConfig() ""Domain"": """" } }, - ""ReflectedWorkItemIDFieldName"": ""nkdScrum.ReflectedWorkItemId"", + ""ReflectedWorkItemIdField"": ""nkdScrum.ReflectedWorkItemId"", ""AllowCrossProjectLinking"": false, ""LanguageMaps"": { ""AreaPath"": ""Area"", diff --git a/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsProcessorTests.cs b/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsProcessorTests.cs index 841ca5e97..9d28c959a 100644 --- a/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsProcessorTests.cs +++ b/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsProcessorTests.cs @@ -51,10 +51,13 @@ protected TfsTeamSettingsProcessor GetTfsTeamSettingsProcessor(TfsTeamSettingsPr { IOptions options = Microsoft.Extensions.Options.Options.Create(new TfsTeamSettingsEndpointOptions() { - Organisation = "https://dev.azure.com/nkdagility-preview/", + Collection = new Uri("https://dev.azure.com/nkdagility-preview/"), Project = "migrationSource1", - AccessToken = TestingConstants.AccessToken, - AuthenticationMode = AuthenticationMode.AccessToken, + Authentication = new TfsAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.AccessToken, + AccessToken = TestingConstants.AccessToken + }, ReflectedWorkItemIdField = "Custom.ReflectedWorkItemId", }); return ActivatorUtilities.CreateInstance(sp, typeof(TfsTeamSettingsEndpoint), options); @@ -63,10 +66,13 @@ protected TfsTeamSettingsProcessor GetTfsTeamSettingsProcessor(TfsTeamSettingsPr { IOptions options = Microsoft.Extensions.Options.Options.Create(new TfsTeamSettingsEndpointOptions() { - Organisation = "https://dev.azure.com/nkdagility-preview/", + Collection = new Uri("https://dev.azure.com/nkdagility-preview/"), Project = "migrationTarget1", - AccessToken = TestingConstants.AccessToken, - AuthenticationMode = AuthenticationMode.AccessToken, + Authentication = new TfsAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.AccessToken, + AccessToken = TestingConstants.AccessToken + }, ReflectedWorkItemIdField = "Custom.ReflectedWorkItemId", }); return ActivatorUtilities.CreateInstance(sp, typeof(TfsTeamSettingsEndpoint), options); diff --git a/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs index 1fd2cc8e3..dd5c2e1a7 100644 --- a/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs +++ b/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.Configuration; +using System; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using MigrationTools.Endpoints; +using MigrationTools.Endpoints.Infrastructure; using MigrationTools.Services; using MigrationTools.Services.Shadows; using MigrationTools.Shadows; @@ -54,10 +56,13 @@ private static TfsTeamSettingsEndpointOptions GetTfsTeamEndPointOptions(string p { return new TfsTeamSettingsEndpointOptions() { - Organisation = "https://dev.azure.com/nkdagility-preview/", + Collection = new Uri("https://dev.azure.com/nkdagility-preview/"), Project = project, - AuthenticationMode = AuthenticationMode.AccessToken, - AccessToken = TestingConstants.AccessToken, + Authentication = new TfsAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.AccessToken, + AccessToken = TestingConstants.AccessToken, + } }; } @@ -75,10 +80,13 @@ private static TfsEndpointOptions GetTfsEndPointOptions(string project) { return new TfsEndpointOptions() { - Organisation = "https://dev.azure.com/nkdagility-preview/", + Collection = new Uri("https://dev.azure.com/nkdagility-preview/"), Project = project, - AuthenticationMode = AuthenticationMode.AccessToken, - AccessToken = TestingConstants.AccessToken, + Authentication = new TfsAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.AccessToken, + AccessToken = TestingConstants.AccessToken, + } }; } } diff --git a/src/MigrationTools.Clients.TfsObjectModel/Clients/TfsWorkItemMigrationClient.cs b/src/MigrationTools.Clients.TfsObjectModel/Clients/TfsWorkItemMigrationClient.cs index 5e0fa7311..073650fdd 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Clients/TfsWorkItemMigrationClient.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Clients/TfsWorkItemMigrationClient.cs @@ -148,12 +148,12 @@ public override ReflectedWorkItemId GetReflectedWorkItemId(WorkItemData workItem Log.Debug("GetReflectedWorkItemId: START"); var local = workItem.ToWorkItem(); - if (!local.Fields.Contains(Options.ReflectedWorkItemIDFieldName)) + if (!local.Fields.Contains(Options.ReflectedWorkItemIdField)) { Log.Debug("GetReflectedWorkItemId: END - no reflected work item id on work item"); return null; } - string rwiid = local.Fields[Options.ReflectedWorkItemIDFieldName].Value.ToString(); + string rwiid = local.Fields[Options.ReflectedWorkItemIdField].Value.ToString(); if (!string.IsNullOrEmpty(rwiid)) { Log.Debug("GetReflectedWorkItemId: END - Has ReflectedWorkItemIdField and has value"); @@ -249,7 +249,7 @@ private Endpoints.IWorkItemQuery GetWorkItemQuery(string WIQLQuery) var wiqb = _workItemQueryBuilderFactory.Create(); wiqb.Query = WIQLQuery; wiqb.AddParameter("TeamProject", Options.Project); - wiqb.AddParameter("ReflectedWorkItemIdFieldName", Options.ReflectedWorkItemIDFieldName); + wiqb.AddParameter("ReflectedWorkItemIdField", Options.ReflectedWorkItemIdField); return wiqb.BuildWIQLQuery(MigrationClient); } } @@ -273,7 +273,7 @@ protected WorkItemData FindReflectedWorkItemByReflectedWorkItemId(ReflectedWorkI var workItemQueryBuilder = CreateReflectedWorkItemQuery(refId.ToString()); var query = workItemQueryBuilder.BuildWIQLQuery(MigrationClient); var items = query.GetWorkItems(); - var reflectedFielName = Options.ReflectedWorkItemIDFieldName; + var reflectedFielName = Options.ReflectedWorkItemIdField; foundWorkItem = items.FirstOrDefault(wi => wi.ToWorkItem().Fields[reflectedFielName].Value.ToString() == refId.ToString()); if (cache && foundWorkItem is not null) { @@ -296,7 +296,7 @@ private IWorkItemQueryBuilder CreateReflectedWorkItemQuery(string refId) workItemQueryBuilder.AddParameter("TeamProject", Options.Project); } - queryBuilder.AppendFormat("[{0}] = @idToFind", Options.ReflectedWorkItemIDFieldName); + queryBuilder.AppendFormat("[{0}] = @idToFind", Options.ReflectedWorkItemIdField); workItemQueryBuilder.AddParameter("idToFind", refId); workItemQueryBuilder.Query = queryBuilder.ToString(); return workItemQueryBuilder; diff --git a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/TfsTeamProjectEndPointOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/TfsTeamProjectEndPointOptions.cs index 2c6994d80..529ca309f 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/TfsTeamProjectEndPointOptions.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/TfsTeamProjectEndPointOptions.cs @@ -9,27 +9,8 @@ namespace MigrationTools.Endpoints { - public class TfsTeamProjectEndpointOptions : EndpointOptions + public class TfsTeamProjectEndpointOptions : TfsEndpointOptions { - public Uri Collection { get; set; } - public string Project { get; set; } - - public TfsAuthenticationOptions Authentication { get; set; } - public string ReflectedWorkItemIDFieldName { get; set; } - public bool AllowCrossProjectLinking { get; set; } - - public TfsLanguageMapOptions LanguageMaps { get; set; } - - [JsonIgnore] - public string CollectionName { get { return GetCollectionName(); } } - - public string GetCollectionName() - { - //var repositoryDescription = new RepositoryDescription(Collection); - //return repositoryDescription.CollectionName; - // Pending fix from https://github.com/bbtsoftware/TfsUrlParser - return Collection != null ? Collection.ToString() : "https://dev.azure.com/sampleAccount"; - } } } \ No newline at end of file diff --git a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpoint.cs b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpoint.cs index f5979785d..de402a3b5 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpoint.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpoint.cs @@ -66,8 +66,8 @@ private TfsTeamProjectCollection GetTfsCollection() using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("GetTfsCollection", ActivityKind.Client)) { activity?.SetTagsFromOptions(Options); - activity?.SetTag("url.full", Options.Organisation); - activity?.SetTag("server.address", Options.Organisation); + activity?.SetTag("url.full", Options.Collection); + activity?.SetTag("server.address", Options.Collection); activity?.SetTag("http.request.method", "GET"); activity?.SetTag("migrationtools.client", "TfsObjectModel"); activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10)); @@ -77,30 +77,30 @@ private TfsTeamProjectCollection GetTfsCollection() VssCredentials vssCredentials; try { - Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection:AuthenticationMode({0})", Options.AuthenticationMode.ToString()); - switch (Options.AuthenticationMode) + Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection:AuthenticationMode({0})", Options?.Authentication?.AuthenticationMode.ToString()); + switch (Options?.Authentication?.AuthenticationMode) { case AuthenticationMode.AccessToken: - Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection: Connecting Using PAT Authentication ", Options.Organisation); - vssCredentials = new VssBasicCredential(string.Empty, Options.AccessToken); - _Collection = new TfsTeamProjectCollection(new Uri(Options.Organisation), vssCredentials); + Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection: Connecting Using PAT Authentication ", Options.Collection); + vssCredentials = new VssBasicCredential(string.Empty, Options?.Authentication?.AccessToken); + _Collection = new TfsTeamProjectCollection(Options.Collection, vssCredentials); break; case AuthenticationMode.Prompt: - Log.LogDebug("TfsWorkItemEndPoint::EnsureDataSource: Connecting Using Interactive Authentication ", Options.Organisation); - _Collection = new TfsTeamProjectCollection(new Uri(Options.Organisation)); + Log.LogDebug("TfsWorkItemEndPoint::EnsureDataSource: Connecting Using Interactive Authentication ", Options.Collection); + _Collection = new TfsTeamProjectCollection(Options.Collection); break; default: - Log.LogDebug("TfsWorkItemEndPoint::EnsureDataSource: Connecting Using Interactive Authentication ", Options.Organisation); - _Collection = new TfsTeamProjectCollection(new Uri(Options.Organisation)); + Log.LogDebug("TfsWorkItemEndPoint::EnsureDataSource: Connecting Using Interactive Authentication ", Options.Collection); + _Collection = new TfsTeamProjectCollection(Options.Collection); break; } Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection: Connected "); Log.LogDebug("TfsWorkItemEndPoint::GetTfsCollection: validating security for {@AuthorizedIdentity} ", _Collection.AuthorizedIdentity); _Collection.EnsureAuthenticated(); - Log.LogInformation("TfsWorkItemEndPoint::GetTfsCollection: Access granted to {CollectionUrl} for {Name} ({Account})", Options.Organisation, _Collection.AuthorizedIdentity.DisplayName, _Collection.AuthorizedIdentity.UniqueName); + Log.LogInformation("TfsWorkItemEndPoint::GetTfsCollection: Access granted to {CollectionUrl} for {Name} ({Account})", Options.Collection, _Collection.AuthorizedIdentity.DisplayName, _Collection.AuthorizedIdentity.UniqueName); activity?.Stop(); activity?.SetStatus(ActivityStatusCode.Ok); activity?.SetTag("http.response.status_code", "200"); @@ -112,7 +112,7 @@ private TfsTeamProjectCollection GetTfsCollection() activity?.SetStatus(ActivityStatusCode.Error); activity?.SetTag("http.response.status_code", "500"); Telemetry.TrackException(ex, null); - Log.LogError(ex, "Unable to connect to {Organisation}", Options.Organisation); + Log.LogError(ex, "Unable to connect to {Organisation}", Options?.Collection); throw; } } @@ -127,8 +127,8 @@ private WorkItemStore GetWorkItemStore(TfsTeamProjectCollection tfs, WorkItemSto using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("GetWorkItemStore", ActivityKind.Client)) { activity?.SetTagsFromOptions(Options); - activity?.SetTag("url.full", Options.Organisation); - activity?.SetTag("server.address", Options.Organisation); + activity?.SetTag("url.full", Options?.Collection); + activity?.SetTag("server.address", Options?.Collection); activity?.SetTag("http.request.method", "GET"); activity?.SetTag("migrationtools.client", "TfsObjectModel"); activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10)); @@ -142,7 +142,7 @@ private WorkItemStore GetWorkItemStore(TfsTeamProjectCollection tfs, WorkItemSto catch (Exception ex) { Telemetry.TrackException(ex, null); - Log.LogError(ex, "Unable to connect to {Organisation} Store", Options.Organisation); + Log.LogError(ex, "Unable to connect to {Organisation} Store", Options.Collection); throw; } finally @@ -166,8 +166,8 @@ private Project GetTfsProject() using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("GetTfsProject", ActivityKind.Client)) { activity?.SetTagsFromOptions(Options); - activity?.SetTag("url.full", Options.Organisation); - activity?.SetTag("server.address", Options.Organisation); + activity?.SetTag("url.full", Options.Collection); + activity?.SetTag("server.address", Options.Collection); activity?.SetTag("http.request.method", "GET"); activity?.SetTag("migrationtools.client", "TfsObjectModel"); activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10)); diff --git a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs index 133f111bc..1918fcc61 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs @@ -1,45 +1,42 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using MigrationTools.Endpoints.Infrastructure; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using TfsUrlParser; namespace MigrationTools.Endpoints { public class TfsEndpointOptions : EndpointOptions { - [JsonConverter(typeof(StringEnumConverter))] - [Required] - public AuthenticationMode AuthenticationMode { get; set; } - - [Required] - public string AccessToken { get; set; } [JsonProperty(Order = -3)] [Required] - public string Organisation { get; set; } + public Uri Collection { get; set; } [JsonProperty(Order = -2)] [Required] public string Project { get; set; } + [Required] + public TfsAuthenticationOptions Authentication { get; set; } + [JsonProperty(Order = -1)] [Required] public string ReflectedWorkItemIdField { get; set; } + public bool AllowCrossProjectLinking { get; set; } [Required] public TfsLanguageMapOptions LanguageMaps { get; set; } + //[JsonIgnore] + //public string CollectionName { get { return GetCollectionName(); } } + + //public string GetCollectionName() + //{ + // //var repositoryDescription = new RepositoryDescription(Collection); + // //return repositoryDescription.CollectionName; + // // Pending fix from https://github.com/bbtsoftware/TfsUrlParser + // return Collection != null ? Collection.ToString() : "https://dev.azure.com/sampleAccount"; + //} - //right now this method is reflection invoked to generate the first settings file - private void SetDefaults() - { - AccessToken = "6i4jyylsadkjanjniaydxnjsi4zsz3qarxhl2y5ngzzffiqdostq"; - Organisation = "https://dev.azure.com/nkdagility-preview/"; - Project = "NeedToSetThis"; - ReflectedWorkItemIdField = "Custom.ReflectedWorkItemId"; - LanguageMaps = new TfsLanguageMapOptions() - { - AreaPath = "Area", - IterationPath = "Iteration" - }; - } } } \ No newline at end of file diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTestPlansAndSuitesMigrationProcessor.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTestPlansAndSuitesMigrationProcessor.cs index d1600d3bf..cfdeeec64 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTestPlansAndSuitesMigrationProcessor.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTestPlansAndSuitesMigrationProcessor.cs @@ -361,7 +361,7 @@ private void AssignReflectedWorkItemId(int sourceWIId, int targetWIId) { var sourceWI = Source.WorkItems.GetWorkItem(sourceWIId.ToString()); var targetWI = Target.WorkItems.GetWorkItem(targetWIId.ToString()); - targetWI.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIDFieldName].Value = Source.WorkItems.CreateReflectedWorkItemId(sourceWI).ToString(); + targetWI.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIdField].Value = Source.WorkItems.CreateReflectedWorkItemId(sourceWI).ToString(); targetWI.SaveToAzureDevOps(); } @@ -592,7 +592,7 @@ private ITestSuiteBase FindSuiteEntry(IStaticTestSuite staticSuite, string title //Get Target ReflectedWorkItemId var targetWI = Target.WorkItems.GetWorkItem(testSuit.Id.ToString()); Log.LogDebug("TestPlansAndSuitesMigrationContext::FindSuiteEntry::TargetWorkItem[{workItemId]", targetWI.Id); - string workItemReflectedId = (string)targetWI.Fields[Target.Options.ReflectedWorkItemIDFieldName].Value; + string workItemReflectedId = (string)targetWI.Fields[Target.Options.ReflectedWorkItemIdField].Value; Log.LogDebug("TestPlansAndSuitesMigrationContext::FindSuiteEntry::TargetWorkItem[{workItemId] [{ReflectedWorkItemId]", targetWI.Id, workItemReflectedId); //Compaire if (workItemReflectedId != expectedReflectedId) @@ -620,12 +620,12 @@ private ITestPlan FindTestPlan(string planName, int sourcePlanId) //Get Target ReflectedWorkItemId var targetWI = Target.WorkItems.GetWorkItem(testPlan.Id.ToString()); Log.LogDebug("TestPlansAndSuitesMigrationContext::FindTestPlan::TargetWorkItem[{workItemId]", targetWI.Id); - if (!targetWI.Fields.ContainsKey(Target.Options.ReflectedWorkItemIDFieldName)) + if (!targetWI.Fields.ContainsKey(Target.Options.ReflectedWorkItemIdField)) { - Log.LogError("TestPlansAndSuitesMigrationContext::FindTestPlan::TargetWorkItem[{workItemId} does not have ReflectedWorkItemId field {ReflectedWorkItemIDFieldName}", targetWI.Id, Target.Options.ReflectedWorkItemIDFieldName); + Log.LogError("TestPlansAndSuitesMigrationContext::FindTestPlan::TargetWorkItem[{workItemId} does not have ReflectedWorkItemId field {ReflectedWorkItemIdField}", targetWI.Id, Target.Options.ReflectedWorkItemIdField); Environment.Exit(-1); } - string workItemReflectedId = (string)targetWI.Fields[Target.Options.ReflectedWorkItemIDFieldName].Value; + string workItemReflectedId = (string)targetWI.Fields[Target.Options.ReflectedWorkItemIdField].Value; Log.LogDebug("TestPlansAndSuitesMigrationContext::FindTestPlan::TargetWorkItem[{workItemId] [{ReflectedWorkItemId]", targetWI.Id, workItemReflectedId); //Compaire if (workItemReflectedId != expectedReflectedId) diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemBulkEditProcessorOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemBulkEditProcessorOptions.cs index 936fc0889..0ae5e018d 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemBulkEditProcessorOptions.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemBulkEditProcessorOptions.cs @@ -48,7 +48,7 @@ public class TfsWorkItemBulkEditProcessorOptions : ProcessorOptions, IWorkItemPr public TfsWorkItemBulkEditProcessorOptions() { - WIQLQuery = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc"; + WIQLQuery = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdField] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc"; } } } \ No newline at end of file diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs index 26cbf2afa..5703698f4 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs @@ -265,7 +265,7 @@ private void ValidateAllWorkItemTypesHaveReflectedWorkItemIdField(List revisionsToMigrate, Work // Todo: Think about an "UpdateChangedBy" flag as this is expensive! (2s/WI instead of 1,5s when writing "Migration") var reflectedUri = (TfsReflectedWorkItemId)Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem); - if (!targetWorkItem.ToWorkItem().Fields.Contains(Target.Options.ReflectedWorkItemIDFieldName)) + if (!targetWorkItem.ToWorkItem().Fields.Contains(Target.Options.ReflectedWorkItemIdField)) { - var ex = new InvalidOperationException("ReflectedWorkItemIDField Field Missing"); + var ex = new InvalidOperationException("ReflectedWorkItemIdField Field Missing"); Log.LogError(ex, " The WorkItemType {WorkItemType} does not have a Field called {ReflectedWorkItemID}", targetWorkItem.Type, - Target.Options.ReflectedWorkItemIDFieldName); + Target.Options.ReflectedWorkItemIdField); throw ex; } - targetWorkItem.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIDFieldName].Value = reflectedUri.ToString(); + targetWorkItem.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIdField].Value = reflectedUri.ToString(); ProcessHTMLFieldAttachements(targetWorkItem); ProcessWorkItemEmbeddedLinks(sourceWorkItem, targetWorkItem); @@ -837,7 +837,7 @@ private WorkItemData ReplayRevisions(List revisionsToMigrate, Work if (Options.GenerateMigrationComment) { - var reflectedUri = targetWorkItem.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIDFieldName].Value; + var reflectedUri = targetWorkItem.ToWorkItem().Fields[Target.Options.ReflectedWorkItemIdField].Value; var history = new StringBuilder(); history.Append( $"This work item was migrated from a different project or organization. You can find the old version at {reflectedUri}."); diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemOverwriteProcessorOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemOverwriteProcessorOptions.cs index e11d57164..85d06b8c8 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemOverwriteProcessorOptions.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemOverwriteProcessorOptions.cs @@ -48,7 +48,7 @@ public class TfsWorkItemPostProcessingProcessorOptions : ProcessorOptions, IWork public TfsWorkItemPostProcessingProcessorOptions() { - WIQLQuery = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdFieldName] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc"; + WIQLQuery = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [@ReflectedWorkItemIdField] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc"; } } } \ No newline at end of file diff --git a/src/MigrationTools.Clients.TfsObjectModel/Tools/TfsValidateRequiredFieldTool.cs b/src/MigrationTools.Clients.TfsObjectModel/Tools/TfsValidateRequiredFieldTool.cs index b5b9f0063..b21b00951 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Tools/TfsValidateRequiredFieldTool.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Tools/TfsValidateRequiredFieldTool.cs @@ -67,13 +67,13 @@ public bool ValidatingRequiredField(TfsProcessor processor, string fieldToFind, //{ // //Make sure that the ReflectedWorkItemId field name specified in the config exists in the target process, preferably on each work item type // var fields = _witClient.GetFieldsAsync(Engine.Target.Config.AsTeamProjectConfig().Project).Result; - // bool rwiidFieldExists = fields.Any(x => x.ReferenceName == Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName || x.Name == Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); + // bool rwiidFieldExists = fields.Any(x => x.ReferenceName == Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIdField || x.Name == Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIdField); // contextLog.Information("Found {FieldsFoundCount} work item fields.", fields.Count.ToString("n0")); // if (rwiidFieldExists) - // contextLog.Information("Found '{ReflectedWorkItemIDFieldName}' in this project, proceeding.", Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); + // contextLog.Information("Found '{ReflectedWorkItemIdField}' in this project, proceeding.", Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIdField); // else // { - // contextLog.Information("Config file specifies '{ReflectedWorkItemIDFieldName}', which wasn't found.", Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); + // contextLog.Information("Config file specifies '{ReflectedWorkItemIdField}', which wasn't found.", Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIdField); // contextLog.Information("Instead, found:"); // foreach (var field in fields.OrderBy(x => x.Name)) // contextLog.Information("{FieldType} - {FieldName} - {FieldRefName}", field.Type.ToString().PadLeft(15), field.Name.PadRight(20), field.ReferenceName ?? ""); diff --git a/src/MigrationTools.Samples/configuration.json b/src/MigrationTools.Samples/configuration.json index 2032e8810..2ad66b3fd 100644 --- a/src/MigrationTools.Samples/configuration.json +++ b/src/MigrationTools.Samples/configuration.json @@ -5,7 +5,7 @@ "Source": { "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "Custom.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "PersonalAccessToken": "", "LanguageMaps": { @@ -16,7 +16,7 @@ "Target": { "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationTarget1", - "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, "PersonalAccessToken": "", "LanguageMaps": { diff --git a/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json b/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json index 1f901d061..e5350e83a 100644 --- a/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json +++ b/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json @@ -4,7 +4,7 @@ "Collection": "https://tfs.test.company.com/tfs/col2/", "Name": "ProjectName" }, - "ReflectedWorkItemIDFieldName": "TfsMigrationTool.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "TfsMigrationTool.ReflectedWorkItemId", "WorkItemTypeDefinition": { "Bug": "Bug", "User Story": "User Story", diff --git a/src/MigrationTools.Samples/demo-migration-reset.json b/src/MigrationTools.Samples/demo-migration-reset.json index 506f76924..31f1231c7 100644 --- a/src/MigrationTools.Samples/demo-migration-reset.json +++ b/src/MigrationTools.Samples/demo-migration-reset.json @@ -4,7 +4,7 @@ "Collection": "https://tfs.test.company.com/tfs/Coll2/", "Name": "ProjectName" }, - "ReflectedWorkItemIDFieldName": "TfsMigrationTool.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "TfsMigrationTool.ReflectedWorkItemId", "WorkItemTypeDefinition": { "Bug": "Bug", "User Story": "User Story", diff --git a/src/MigrationTools.Samples/demo-migration.json b/src/MigrationTools.Samples/demo-migration.json index 3c2bf6f3a..152e4a453 100644 --- a/src/MigrationTools.Samples/demo-migration.json +++ b/src/MigrationTools.Samples/demo-migration.json @@ -8,7 +8,7 @@ "Collection": "https://tfs.test.company.com/tfs/coll1/", "Name": "ProjectName_New" }, - "ReflectedWorkItemIDFieldName": "TfsMigrationTool.ReflectedWorkItemId", + "ReflectedWorkItemIdField": "TfsMigrationTool.ReflectedWorkItemId", "WorkItemTypeDefinition": { "Bug": "Bug", "User Story": "User Story", diff --git a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs index ff630201f..1cb59f30b 100644 --- a/src/MigrationTools/Options/OptionsConfigurationBuilder.cs +++ b/src/MigrationTools/Options/OptionsConfigurationBuilder.cs @@ -7,31 +7,31 @@ using Elmah.Io.Client; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.TeamFoundation.Framework.Common; using Microsoft.VisualStudio.Services.Audit; using Microsoft.VisualStudio.Services.Common.CommandLine; using MigrationTools.EndpointEnrichers; using MigrationTools.Endpoints.Infrastructure; using MigrationTools.Enrichers; +using MigrationTools.Processors.Infrastructure; +using MigrationTools.Tools.Infrastructure; using Newtonsoft.Json.Linq; using Serilog.Core; namespace MigrationTools.Options { - public class OptionItem - { - string typeName { get; set; } - string key { get; set; } - IOptions Option { get; set; } - } + public class OptionsConfigurationBuilder { + + readonly ILogger logger; readonly IConfiguration configuration; + - private List OptionsToInclude { get; } - private Dictionary NamedOptionsToInclude { get; } + OptionsContainer optionsContainer; private List catalogue; @@ -42,32 +42,25 @@ public OptionsConfigurationBuilder( { this.configuration = configuration; this.logger = logger; - OptionsToInclude = new List(); - NamedOptionsToInclude = new Dictionary(); + optionsContainer = new OptionsContainer(); catalogue = AppDomain.CurrentDomain.GetMigrationToolsTypes().WithInterface().ToList(); } - public void AddAllOptions() + public IReadOnlyList GetOptions() { - var keyGen = new KeyGenerator(); + return optionsContainer.GetOptions(); + } + + public IReadOnlyList GetOptions(OptionItemType optionType) + { + return optionsContainer.GetOptions(optionType); + } + public void AddAllOptions() + { foreach (var optionType in catalogue) { - switch (optionType) - { - case Type t when typeof(IEndpointOptions).IsAssignableFrom(t): - AddOption(optionType.Name, keyGen.GetNextKey()); - break; - case Type t when typeof(IProcessorEnricherOptions).IsAssignableFrom(t): - logger.LogWarning("Skipping ProcessorEnricherOptions: {optionType}", optionType.Name); - break; - case Type t when typeof(IEndpointEnricherOptions).IsAssignableFrom(t): - logger.LogWarning("Skipping ProcessorEnricherOptions: {optionType}", optionType.Name); - break; - default: - AddOption(optionType.Name); - break; - } + optionsContainer.AddOption(new OptionItem(CreateOptionFromType(optionType))); } } @@ -75,10 +68,23 @@ public void AddOption(IOptions option) { if (option != null) { - OptionsToInclude.Add(option); + optionsContainer.AddOption(new OptionItem(option)); + } + else + { + logger.LogWarning("Could not add Option as it was null!"); + } + } + + + public void AddOption(OptionItem option) + { + if (option != null && option.Option !=null ) + { + optionsContainer.AddOption(option); } else { - logger.LogWarning("Could not add option as it was null"); + logger.LogWarning("Could not add OptionItem as it was null or its IOption was!"); } } @@ -86,7 +92,10 @@ public void AddOption(IEnumerable options) { if (options != null) { - OptionsToInclude.AddRange(options); + foreach (var item in options) + { + AddOption(item); + } } else { logger.LogWarning("Could not add options as they were null"); @@ -94,18 +103,14 @@ public void AddOption(IEnumerable options) } public void AddOption(string optionName) + { + AddOption(CreateOptionFromString(optionName)); + } + private IOptions CreateOptionFromString(string optionName) { optionName = optionName.Replace("Options", ""); var optionType = catalogue.FirstOrDefault(x => x.Name.StartsWith(optionName)); - if (optionType == null) - { - logger.LogWarning("Could not find option type for {optionName}", optionName); - } else - { - logger.LogDebug("Adding {optionName}", optionName); - OptionsToInclude.Add(CreateOptionFromType(optionType)); - } - + return CreateOptionFromType(optionType); } private IOptions CreateOptionFromType(Type optionType) @@ -120,7 +125,7 @@ public void AddOption(IOptions option, string key) { if (option != null) { - NamedOptionsToInclude.Add(key, option); + optionsContainer.AddOption(new OptionItem(key, option)); } else { logger.LogWarning("Could not add option as it was null"); @@ -129,17 +134,7 @@ public void AddOption(IOptions option, string key) public void AddOption(string optionName, string key) { - optionName = optionName.Replace("Options", ""); - var optionType = catalogue.FirstOrDefault(x => x.Name.StartsWith(optionName)); - if (optionType == null) - { - logger.LogWarning("Could not find option type for {optionName}", optionName); - } - else - { - logger.LogDebug("Adding {optionName} as {key}", optionName, key); - NamedOptionsToInclude.Add(key, CreateOptionFromType(optionType)); - } + optionsContainer.AddOption(new OptionItem(key, CreateOptionFromString(optionName))); } public string Build() @@ -154,13 +149,17 @@ public string Build() configJson["MigrationTools"]["Endpoints"] = new JObject(); configJson["MigrationTools"]["Processors"] = new JArray(); configJson["MigrationTools"]["CommonTools"] = new JObject(); - foreach (var item in OptionsToInclude) - { - configJson = AddOptionToConfig(configuration, configJson, item); - } - foreach (var item in NamedOptionsToInclude) + foreach (var item in optionsContainer.GetOptions()) { - configJson = AddNamedOptionToConfig(configuration, configJson, item.Key, item.Value); + if (item.IsKeyRequired) + { + configJson = AddNamedOptionToConfig(configuration, configJson, item.key, item.Option); + } + else + { + configJson = AddOptionToConfig(configuration, configJson, item.Option); + } + } return configJson.ToString(Newtonsoft.Json.Formatting.Indented); } @@ -221,11 +220,147 @@ private JObject AddOptionToConfig(IConfiguration configuration, JObject configJs public class KeyGenerator { private int _counter = 1; + private Dictionary _nameCounters = new Dictionary(); + // Generate a key without a name (global counter) public string GetNextKey() { _counter++; - return $"Key{_counter}"; + return $"Refname{_counter}"; } + + // Generate a key for a specific name (name-based counter) + public string GetNextKey(string name) + { + // Check if the name already exists in the dictionary + if (!_nameCounters.ContainsKey(name)) + { + // If not, initialize its counter to 1 + _nameCounters[name] = 1; + } + else + { + // Increment the counter for the given name + _nameCounters[name]++; + } + + return $"{name}Refname{_nameCounters[name]}"; + } + } + + + public enum OptionItemType + { + Unknown, + Processor, + EndpointEnricher, + ProcessorEnricher, + Endpoint, + Tool, } + + public class OptionItem + { + private static readonly KeyGenerator keyGen = new KeyGenerator(); + public OptionItemType Type { get; set; } + public bool IsKeyRequired { get; } + public string key { get; set; } + public IOptions Option { get; set; } + + public OptionItem(string key, IOptions option) + { + Type = GetOptionItemType(option); + IsKeyRequired = GetOptionItemKeyRequired(option); + this.key = key; + Option = option; + } + + public OptionItem(IOptions option) + { + Type = GetOptionItemType(option); + IsKeyRequired = GetOptionItemKeyRequired(option); + this.key = keyGen.GetNextKey(Type.ToString()); + Option = option; + } + + private static OptionItemType GetOptionItemType(IOptions option) + { + OptionItemType optionItemType; + var optionType = option.GetType(); + switch (optionType) + { + case Type t when typeof(IEndpointOptions).IsAssignableFrom(t): + optionItemType = OptionItemType.Endpoint; + break; + case Type t when typeof(IProcessorEnricherOptions).IsAssignableFrom(t): + optionItemType = OptionItemType.ProcessorEnricher; + break; + case Type t when typeof(IEndpointEnricherOptions).IsAssignableFrom(t): + optionItemType = OptionItemType.EndpointEnricher; + break; + case Type t when typeof(IProcessorOptions).IsAssignableFrom(t): + optionItemType = OptionItemType.Processor; + break; + case Type t when typeof(IToolOptions).IsAssignableFrom(t): + optionItemType = OptionItemType.Tool; + break; + default: + optionItemType = OptionItemType.Unknown; + break; + } + + return optionItemType; + } + private static bool GetOptionItemKeyRequired(IOptions option) + { + var optionType = option.GetType(); + switch (optionType) + { + case Type t when typeof(IEndpointOptions).IsAssignableFrom(t): + return true; + default: + return false; + } + } + + + } + + internal class OptionsContainer + { + private List OptionsToInclude { get; } + + public OptionsContainer() + { + OptionsToInclude = new List(); + } + + public void AddOption(OptionItem option) + { + if (option != null) + { + OptionsToInclude.Add(option); + } + } + + public IReadOnlyList GetOptions() + { + return OptionsToInclude + .Select(option => option) + .ToList() + .AsReadOnly(); + } + + public IReadOnlyList GetOptions(OptionItemType optionType) + { + return OptionsToInclude + .Where(option => option.Type == optionType) + .Select(option => option) + .ToList() + .AsReadOnly(); + } + + + } + } From 8bcfb8dbb8d7ca68a38cbb8f521d7338a03d507a Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 13:17:54 +0100 Subject: [PATCH 09/11] Update docs --- appsettings.json | 39 ++++ .../reference.endpoints.tfsendpoint.yaml | 96 +++++++-- ...nce.endpoints.tfsteamsettingsendpoint.yaml | 22 +- ...ference.endpoints.tfsworkitemendpoint.yaml | 22 +- ...ssors.keepoutboundlinktargetprocessor.yaml | 2 +- ...cessors.tfsworkitemmigrationprocessor.yaml | 167 ++++++++++++++++ .../reference.endpoints.tfsendpoint.md | 96 +++++++-- ...rence.endpoints.tfsteamsettingsendpoint.md | 22 +- ...reference.endpoints.tfsworkitemendpoint.md | 22 +- ...cessors.keepoutboundlinktargetprocessor.md | 2 +- ...rocessors.tfsworkitemmigrationprocessor.md | 189 ++++++++++++++++++ 11 files changed, 603 insertions(+), 76 deletions(-) create mode 100644 docs/_data/reference.processors.tfsworkitemmigrationprocessor.yaml create mode 100644 docs/collections/_reference/reference.processors.tfsworkitemmigrationprocessor.md diff --git a/appsettings.json b/appsettings.json index 6bc670e93..1be2324a3 100644 --- a/appsettings.json +++ b/appsettings.json @@ -13,6 +13,26 @@ }, "MigrationTools": { "EndpointDefaults": { + "TfsEndpoint": { + "EndpointType": "TfsTeamProjectEndpoint", + "Collection": "", + "Project": "", + "AllowCrossProjectLinking": false, + "Authentication": { + "AuthenticationMode": "AccessToken", + "AccessToken": "12345", + "NetworkCredentials": { + "UserName": "", + "Password": "", + "Domain": "" + } + }, + "AuthenticationMode": "AccessToken", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + } + }, "TfsTeamProjectEndpoint": { "EndpointType": "TfsTeamProjectEndpoint", "Collection": "", @@ -53,6 +73,25 @@ "AreaPath": "Area", "IterationPath": "Iteration" } + }, + "TfsEndpoint": { + "EndpointType": "TfsTeamProjectEndpoint", + "Collection": "https://dev.azure.com/nkdagility-preview/", + "Project": "migrationSource1", + "AllowCrossProjectLinking": false, + "Authentication": { + "AuthenticationMode": "AccessToken", + "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad", + "NetworkCredentials": { + "UserName": "", + "Password": "", + "Domain": "" + } + }, + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + } } }, "CommonTools": { diff --git a/docs/_data/reference.endpoints.tfsendpoint.yaml b/docs/_data/reference.endpoints.tfsendpoint.yaml index 928643c5a..913a0fa41 100644 --- a/docs/_data/reference.endpoints.tfsendpoint.yaml +++ b/docs/_data/reference.endpoints.tfsendpoint.yaml @@ -3,23 +3,89 @@ optionsClassFullName: MigrationTools.Endpoints.TfsEndpointOptions configurationSamples: - name: defaults description: - code: There are no defaults! Check the sample for options! + code: >- + { + "MigrationTools": { + "Endpoints": { + "#KEY#": { + "TfsEndpoint": { + "AllowCrossProjectLinking": "False", + "Authentication": { + "AccessToken": "12345", + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "Password": "", + "UserName": "" + } + }, + "AuthenticationMode": "AccessToken", + "Collection": "", + "EndpointType": "TfsTeamProjectEndpoint", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, + "Project": "" + } + } + } + } + } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions - name: sample description: - code: There is no sample, but you can check the classic below for a general feel. + code: >- + { + "MigrationTools": { + "Endpoints": { + "#KEY#": { + "TfsEndpoint": { + "AllowCrossProjectLinking": "False", + "Authentication": { + "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad", + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "Password": "", + "UserName": "" + } + }, + "Collection": "https://dev.azure.com/nkdagility-preview/", + "EndpointType": "TfsTeamProjectEndpoint", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, + "Project": "migrationSource1" + } + } + } + } + } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions - name: classic description: code: >- { "$type": "TfsEndpointOptions", - "Organisation": null, - "Project": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Collection": "https://dev.azure.com/nkdagility-preview/", + "Project": "migrationSource1", + "Authentication": { + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "UserName": "", + "Password": "" + }, + "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad" + }, "ReflectedWorkItemIdField": null, - "LanguageMaps": null, + "AllowCrossProjectLinking": false, + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, "EndpointEnrichers": null } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions @@ -28,12 +94,16 @@ className: TfsEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -44,10 +114,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/_data/reference.endpoints.tfsteamsettingsendpoint.yaml b/docs/_data/reference.endpoints.tfsteamsettingsendpoint.yaml index d7aca2cd7..5a00d7af0 100644 --- a/docs/_data/reference.endpoints.tfsteamsettingsendpoint.yaml +++ b/docs/_data/reference.endpoints.tfsteamsettingsendpoint.yaml @@ -14,11 +14,11 @@ configurationSamples: code: >- { "$type": "TfsTeamSettingsEndpointOptions", - "Organisation": null, + "Collection": null, "Project": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Authentication": null, "ReflectedWorkItemIdField": null, + "AllowCrossProjectLinking": false, "LanguageMaps": null, "EndpointEnrichers": null } @@ -28,12 +28,16 @@ className: TfsTeamSettingsEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -44,10 +48,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/_data/reference.endpoints.tfsworkitemendpoint.yaml b/docs/_data/reference.endpoints.tfsworkitemendpoint.yaml index 2e177daed..92e536420 100644 --- a/docs/_data/reference.endpoints.tfsworkitemendpoint.yaml +++ b/docs/_data/reference.endpoints.tfsworkitemendpoint.yaml @@ -14,12 +14,12 @@ configurationSamples: code: >- { "$type": "TfsWorkItemEndpointOptions", - "Organisation": null, + "Collection": null, "Project": null, "Query": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Authentication": null, "ReflectedWorkItemIdField": null, + "AllowCrossProjectLinking": false, "LanguageMaps": null, "EndpointEnrichers": null } @@ -29,12 +29,16 @@ className: TfsWorkItemEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -45,10 +49,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/_data/reference.processors.keepoutboundlinktargetprocessor.yaml b/docs/_data/reference.processors.keepoutboundlinktargetprocessor.yaml index a83abc285..3845cb676 100644 --- a/docs/_data/reference.processors.keepoutboundlinktargetprocessor.yaml +++ b/docs/_data/reference.processors.keepoutboundlinktargetprocessor.yaml @@ -17,7 +17,7 @@ configurationSamples: "Enabled": false, "WIQLQuery": "Select [System.Id] From WorkItems Where [System.TeamProject] = @project and not [System.WorkItemType] contains 'Test Suite, Test Plan,Shared Steps,Shared Parameter,Feedback Request'", "TargetLinksToKeepOrganization": "https://dev.azure.com/nkdagility", - "TargetLinksToKeepProject": "9fe99d20-153f-4861-9eee-c0cc7ab8de4d", + "TargetLinksToKeepProject": "5f1e7946-3c7a-4168-866b-8d1451f41fe3", "CleanupFileName": "c:/temp/OutboundLinkTargets.bat", "PrependCommand": "start", "DryRun": true, diff --git a/docs/_data/reference.processors.tfsworkitemmigrationprocessor.yaml b/docs/_data/reference.processors.tfsworkitemmigrationprocessor.yaml new file mode 100644 index 000000000..59bd32bfb --- /dev/null +++ b/docs/_data/reference.processors.tfsworkitemmigrationprocessor.yaml @@ -0,0 +1,167 @@ +optionsClassName: TfsWorkItemMigrationProcessorOptions +optionsClassFullName: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +configurationSamples: +- name: defaults + description: + code: >- + { + "MigrationTools": { + "Processors": [ + { + "ProcessorType": "TfsWorkItemMigrationProcessor", + "AttachRevisionHistory": "False", + "Enabled": "False", + "FilterWorkItemsThatAlreadyExistInTarget": "False", + "FixHtmlAttachmentLinks": "True", + "GenerateMigrationComment": "True", + "MaxGracefulFailures": "0", + "PauseAfterEachWorkItem": "False", + "SkipRevisionWithInvalidAreaPath": "False", + "SkipRevisionWithInvalidIterationPath": "False", + "SourceName": "Source", + "TargetName": "Target", + "UpdateCreatedBy": "True", + "UpdateCreatedDate": "True", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WorkItemCreateRetryLimit": "5", + "WorkItemIDs": null + } + ] + } + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +- name: sample + description: + code: >- + { + "MigrationTools": { + "Processors": [ + { + "ProcessorType": "TfsWorkItemMigrationProcessor", + "AttachRevisionHistory": "False", + "Enabled": "False", + "FilterWorkItemsThatAlreadyExistInTarget": "False", + "FixHtmlAttachmentLinks": "True", + "GenerateMigrationComment": "True", + "MaxGracefulFailures": "0", + "PauseAfterEachWorkItem": "False", + "SkipRevisionWithInvalidAreaPath": "False", + "SkipRevisionWithInvalidIterationPath": "False", + "SourceName": "Source", + "TargetName": "Target", + "UpdateCreatedBy": "True", + "UpdateCreatedDate": "True", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WorkItemCreateRetryLimit": "5", + "WorkItemIDs": null + } + ] + } + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +- name: classic + description: + code: >- + { + "$type": "TfsWorkItemMigrationProcessorOptions", + "Enabled": false, + "UpdateCreatedDate": true, + "UpdateCreatedBy": true, + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "FixHtmlAttachmentLinks": true, + "WorkItemCreateRetryLimit": 5, + "FilterWorkItemsThatAlreadyExistInTarget": false, + "PauseAfterEachWorkItem": false, + "AttachRevisionHistory": false, + "GenerateMigrationComment": true, + "WorkItemIDs": null, + "MaxGracefulFailures": 0, + "SkipRevisionWithInvalidIterationPath": false, + "SkipRevisionWithInvalidAreaPath": false, + "Enrichers": null, + "SourceName": "Source", + "TargetName": "Target", + "RefName": null + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure. +className: TfsWorkItemMigrationProcessor +typeName: Processors +architecture: +options: +- parameterName: AttachRevisionHistory + type: Boolean + description: This will create a json file with the revision history and attach it to the work item. Best used with `MaxRevisions` or `ReplayRevisions`. + defaultValue: '?' +- parameterName: Enabled + type: Boolean + description: If set to `true` then the processor will run. Set to `false` and the processor will not run. + defaultValue: missng XML code comments +- parameterName: Enrichers + type: List + description: List of Enrichers that can be used to add more features to this processor. Only works with Native Processors and not legacy Processors. + defaultValue: missng XML code comments +- parameterName: FilterWorkItemsThatAlreadyExistInTarget + type: Boolean + description: This loads all of the work items already saved to the Target and removes them from the Source work item list prior to commencing the run. While this may take some time in large data sets it reduces the time of the overall migration significantly if you need to restart. + defaultValue: true +- parameterName: FixHtmlAttachmentLinks + type: Boolean + description: "**beta** If enabled this will fix any image attachments URL's, work item mention URL's or user mentions in the HTML fields as well as discussion comments. You must specify a PersonalAccessToken in the Source project for Azure DevOps; TFS should use integrated authentication." + defaultValue: '?' +- parameterName: GenerateMigrationComment + type: Boolean + description: If enabled, adds a comment recording the migration + defaultValue: false +- parameterName: MaxGracefulFailures + type: Int32 + description: The maximum number of failures to tolerate before the migration fails. When set above zero, a work item migration error is logged but the migration will continue until the number of failed items reaches the configured value, after which the migration fails. + defaultValue: 0 +- parameterName: PauseAfterEachWorkItem + type: Boolean + description: Pause after each work item is migrated + defaultValue: false +- parameterName: RefName + type: String + description: '`Refname` will be used in the future to allow for using named Options without the need to copy all of the options.' + defaultValue: missng XML code comments +- parameterName: SkipRevisionWithInvalidAreaPath + type: Boolean + description: When set to true, this setting will skip a revision if the source area has not been migrated, has been deleted or is somehow invalid, etc. + defaultValue: missng XML code comments +- parameterName: SkipRevisionWithInvalidIterationPath + type: Boolean + description: This will skip a revision if the source iteration has not been migrated i.e. it was deleted + defaultValue: missng XML code comments +- parameterName: SourceName + type: String + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: TargetName + type: String + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: UpdateCreatedBy + type: Boolean + description: "If this is enabled the creation process on the target project will create the items with the original creation date. (Important: The item history is always pointed to the date of the migration, it's change only the data column CreateDate, not the internal create date)" + defaultValue: true +- parameterName: UpdateCreatedDate + type: Boolean + description: "If this is enabled the creation process on the target project will create the items with the original creation date. (Important: The item history is always pointed to the date of the migration, it's change only the data column CreateDate, not the internal create date)" + defaultValue: true +- parameterName: WIQLQuery + type: String + description: A work item query based on WIQL to select only important work items. To migrate all leave this empty. See [WIQL Query Bits](#wiql-query-bits) + defaultValue: SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [[System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc +- parameterName: WorkItemCreateRetryLimit + type: Int32 + description: '**beta** If set to a number greater than 0 work items that fail to save will retry after a number of seconds equal to the retry count. This allows for periodic network glitches not to end the process.' + defaultValue: 5 +- parameterName: WorkItemIDs + type: IList + description: A list of work items to import + defaultValue: '[]' +status: ready +processingTarget: Work Items +classFile: /src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs +optionsClassFile: /src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs diff --git a/docs/collections/_reference/reference.endpoints.tfsendpoint.md b/docs/collections/_reference/reference.endpoints.tfsendpoint.md index 4d3d2cc77..9a3302732 100644 --- a/docs/collections/_reference/reference.endpoints.tfsendpoint.md +++ b/docs/collections/_reference/reference.endpoints.tfsendpoint.md @@ -4,23 +4,89 @@ optionsClassFullName: MigrationTools.Endpoints.TfsEndpointOptions configurationSamples: - name: defaults description: - code: There are no defaults! Check the sample for options! + code: >- + { + "MigrationTools": { + "Endpoints": { + "#KEY#": { + "TfsEndpoint": { + "AllowCrossProjectLinking": "False", + "Authentication": { + "AccessToken": "12345", + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "Password": "", + "UserName": "" + } + }, + "AuthenticationMode": "AccessToken", + "Collection": "", + "EndpointType": "TfsTeamProjectEndpoint", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, + "Project": "" + } + } + } + } + } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions - name: sample description: - code: There is no sample, but you can check the classic below for a general feel. + code: >- + { + "MigrationTools": { + "Endpoints": { + "#KEY#": { + "TfsEndpoint": { + "AllowCrossProjectLinking": "False", + "Authentication": { + "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad", + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "Password": "", + "UserName": "" + } + }, + "Collection": "https://dev.azure.com/nkdagility-preview/", + "EndpointType": "TfsTeamProjectEndpoint", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, + "Project": "migrationSource1" + } + } + } + } + } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions - name: classic description: code: >- { "$type": "TfsEndpointOptions", - "Organisation": null, - "Project": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Collection": "https://dev.azure.com/nkdagility-preview/", + "Project": "migrationSource1", + "Authentication": { + "AuthenticationMode": "AccessToken", + "NetworkCredentials": { + "Domain": "", + "UserName": "", + "Password": "" + }, + "AccessToken": "jklsadhjksahfkjsdhjksahsadjhksadhsad" + }, "ReflectedWorkItemIdField": null, - "LanguageMaps": null, + "AllowCrossProjectLinking": false, + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + }, "EndpointEnrichers": null } sampleFor: MigrationTools.Endpoints.TfsEndpointOptions @@ -29,12 +95,16 @@ className: TfsEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -45,10 +115,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/collections/_reference/reference.endpoints.tfsteamsettingsendpoint.md b/docs/collections/_reference/reference.endpoints.tfsteamsettingsendpoint.md index 0f03e9859..d9d83f7ce 100644 --- a/docs/collections/_reference/reference.endpoints.tfsteamsettingsendpoint.md +++ b/docs/collections/_reference/reference.endpoints.tfsteamsettingsendpoint.md @@ -15,11 +15,11 @@ configurationSamples: code: >- { "$type": "TfsTeamSettingsEndpointOptions", - "Organisation": null, + "Collection": null, "Project": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Authentication": null, "ReflectedWorkItemIdField": null, + "AllowCrossProjectLinking": false, "LanguageMaps": null, "EndpointEnrichers": null } @@ -29,12 +29,16 @@ className: TfsTeamSettingsEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -45,10 +49,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/collections/_reference/reference.endpoints.tfsworkitemendpoint.md b/docs/collections/_reference/reference.endpoints.tfsworkitemendpoint.md index 3abcce2e0..74b34d0ad 100644 --- a/docs/collections/_reference/reference.endpoints.tfsworkitemendpoint.md +++ b/docs/collections/_reference/reference.endpoints.tfsworkitemendpoint.md @@ -15,12 +15,12 @@ configurationSamples: code: >- { "$type": "TfsWorkItemEndpointOptions", - "Organisation": null, + "Collection": null, "Project": null, "Query": null, - "AuthenticationMode": "AccessToken", - "AccessToken": null, + "Authentication": null, "ReflectedWorkItemIdField": null, + "AllowCrossProjectLinking": false, "LanguageMaps": null, "EndpointEnrichers": null } @@ -30,12 +30,16 @@ className: TfsWorkItemEndpoint typeName: Endpoints architecture: options: -- parameterName: AccessToken - type: String +- parameterName: AllowCrossProjectLinking + type: Boolean + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: Authentication + type: TfsAuthenticationOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: AuthenticationMode - type: AuthenticationMode +- parameterName: Collection + type: Uri description: missng XML code comments defaultValue: missng XML code comments - parameterName: EndpointEnrichers @@ -46,10 +50,6 @@ options: type: TfsLanguageMapOptions description: missng XML code comments defaultValue: missng XML code comments -- parameterName: Organisation - type: String - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: Project type: String description: missng XML code comments diff --git a/docs/collections/_reference/reference.processors.keepoutboundlinktargetprocessor.md b/docs/collections/_reference/reference.processors.keepoutboundlinktargetprocessor.md index 82a73d8f8..fc50b458d 100644 --- a/docs/collections/_reference/reference.processors.keepoutboundlinktargetprocessor.md +++ b/docs/collections/_reference/reference.processors.keepoutboundlinktargetprocessor.md @@ -18,7 +18,7 @@ configurationSamples: "Enabled": false, "WIQLQuery": "Select [System.Id] From WorkItems Where [System.TeamProject] = @project and not [System.WorkItemType] contains 'Test Suite, Test Plan,Shared Steps,Shared Parameter,Feedback Request'", "TargetLinksToKeepOrganization": "https://dev.azure.com/nkdagility", - "TargetLinksToKeepProject": "9fe99d20-153f-4861-9eee-c0cc7ab8de4d", + "TargetLinksToKeepProject": "5f1e7946-3c7a-4168-866b-8d1451f41fe3", "CleanupFileName": "c:/temp/OutboundLinkTargets.bat", "PrependCommand": "start", "DryRun": true, diff --git a/docs/collections/_reference/reference.processors.tfsworkitemmigrationprocessor.md b/docs/collections/_reference/reference.processors.tfsworkitemmigrationprocessor.md new file mode 100644 index 000000000..66fb11461 --- /dev/null +++ b/docs/collections/_reference/reference.processors.tfsworkitemmigrationprocessor.md @@ -0,0 +1,189 @@ +--- +optionsClassName: TfsWorkItemMigrationProcessorOptions +optionsClassFullName: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +configurationSamples: +- name: defaults + description: + code: >- + { + "MigrationTools": { + "Processors": [ + { + "ProcessorType": "TfsWorkItemMigrationProcessor", + "AttachRevisionHistory": "False", + "Enabled": "False", + "FilterWorkItemsThatAlreadyExistInTarget": "False", + "FixHtmlAttachmentLinks": "True", + "GenerateMigrationComment": "True", + "MaxGracefulFailures": "0", + "PauseAfterEachWorkItem": "False", + "SkipRevisionWithInvalidAreaPath": "False", + "SkipRevisionWithInvalidIterationPath": "False", + "SourceName": "Source", + "TargetName": "Target", + "UpdateCreatedBy": "True", + "UpdateCreatedDate": "True", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WorkItemCreateRetryLimit": "5", + "WorkItemIDs": null + } + ] + } + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +- name: sample + description: + code: >- + { + "MigrationTools": { + "Processors": [ + { + "ProcessorType": "TfsWorkItemMigrationProcessor", + "AttachRevisionHistory": "False", + "Enabled": "False", + "FilterWorkItemsThatAlreadyExistInTarget": "False", + "FixHtmlAttachmentLinks": "True", + "GenerateMigrationComment": "True", + "MaxGracefulFailures": "0", + "PauseAfterEachWorkItem": "False", + "SkipRevisionWithInvalidAreaPath": "False", + "SkipRevisionWithInvalidIterationPath": "False", + "SourceName": "Source", + "TargetName": "Target", + "UpdateCreatedBy": "True", + "UpdateCreatedDate": "True", + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "WorkItemCreateRetryLimit": "5", + "WorkItemIDs": null + } + ] + } + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +- name: classic + description: + code: >- + { + "$type": "TfsWorkItemMigrationProcessorOptions", + "Enabled": false, + "UpdateCreatedDate": true, + "UpdateCreatedBy": true, + "WIQLQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc", + "FixHtmlAttachmentLinks": true, + "WorkItemCreateRetryLimit": 5, + "FilterWorkItemsThatAlreadyExistInTarget": false, + "PauseAfterEachWorkItem": false, + "AttachRevisionHistory": false, + "GenerateMigrationComment": true, + "WorkItemIDs": null, + "MaxGracefulFailures": 0, + "SkipRevisionWithInvalidIterationPath": false, + "SkipRevisionWithInvalidAreaPath": false, + "Enrichers": null, + "SourceName": "Source", + "TargetName": "Target", + "RefName": null + } + sampleFor: MigrationTools.Processors.TfsWorkItemMigrationProcessorOptions +description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure. +className: TfsWorkItemMigrationProcessor +typeName: Processors +architecture: +options: +- parameterName: AttachRevisionHistory + type: Boolean + description: This will create a json file with the revision history and attach it to the work item. Best used with `MaxRevisions` or `ReplayRevisions`. + defaultValue: '?' +- parameterName: Enabled + type: Boolean + description: If set to `true` then the processor will run. Set to `false` and the processor will not run. + defaultValue: missng XML code comments +- parameterName: Enrichers + type: List + description: List of Enrichers that can be used to add more features to this processor. Only works with Native Processors and not legacy Processors. + defaultValue: missng XML code comments +- parameterName: FilterWorkItemsThatAlreadyExistInTarget + type: Boolean + description: This loads all of the work items already saved to the Target and removes them from the Source work item list prior to commencing the run. While this may take some time in large data sets it reduces the time of the overall migration significantly if you need to restart. + defaultValue: true +- parameterName: FixHtmlAttachmentLinks + type: Boolean + description: "**beta** If enabled this will fix any image attachments URL's, work item mention URL's or user mentions in the HTML fields as well as discussion comments. You must specify a PersonalAccessToken in the Source project for Azure DevOps; TFS should use integrated authentication." + defaultValue: '?' +- parameterName: GenerateMigrationComment + type: Boolean + description: If enabled, adds a comment recording the migration + defaultValue: false +- parameterName: MaxGracefulFailures + type: Int32 + description: The maximum number of failures to tolerate before the migration fails. When set above zero, a work item migration error is logged but the migration will continue until the number of failed items reaches the configured value, after which the migration fails. + defaultValue: 0 +- parameterName: PauseAfterEachWorkItem + type: Boolean + description: Pause after each work item is migrated + defaultValue: false +- parameterName: RefName + type: String + description: '`Refname` will be used in the future to allow for using named Options without the need to copy all of the options.' + defaultValue: missng XML code comments +- parameterName: SkipRevisionWithInvalidAreaPath + type: Boolean + description: When set to true, this setting will skip a revision if the source area has not been migrated, has been deleted or is somehow invalid, etc. + defaultValue: missng XML code comments +- parameterName: SkipRevisionWithInvalidIterationPath + type: Boolean + description: This will skip a revision if the source iteration has not been migrated i.e. it was deleted + defaultValue: missng XML code comments +- parameterName: SourceName + type: String + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: TargetName + type: String + description: missng XML code comments + defaultValue: missng XML code comments +- parameterName: UpdateCreatedBy + type: Boolean + description: "If this is enabled the creation process on the target project will create the items with the original creation date. (Important: The item history is always pointed to the date of the migration, it's change only the data column CreateDate, not the internal create date)" + defaultValue: true +- parameterName: UpdateCreatedDate + type: Boolean + description: "If this is enabled the creation process on the target project will create the items with the original creation date. (Important: The item history is always pointed to the date of the migration, it's change only the data column CreateDate, not the internal create date)" + defaultValue: true +- parameterName: WIQLQuery + type: String + description: A work item query based on WIQL to select only important work items. To migrate all leave this empty. See [WIQL Query Bits](#wiql-query-bits) + defaultValue: SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [[System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') ORDER BY [System.ChangedDate] desc +- parameterName: WorkItemCreateRetryLimit + type: Int32 + description: '**beta** If set to a number greater than 0 work items that fail to save will retry after a number of seconds equal to the retry count. This allows for periodic network glitches not to end the process.' + defaultValue: 5 +- parameterName: WorkItemIDs + type: IList + description: A list of work items to import + defaultValue: '[]' +status: ready +processingTarget: Work Items +classFile: /src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs +optionsClassFile: /src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs + +redirectFrom: +- /Reference/Processors/TfsWorkItemMigrationProcessorOptions/ +layout: reference +toc: true +permalink: /Reference/Processors/TfsWorkItemMigrationProcessor/ +title: TfsWorkItemMigrationProcessor +categories: +- Processors +- +topics: +- topic: notes + path: /docs/Reference/Processors/TfsWorkItemMigrationProcessor-notes.md + exists: false + markdown: '' +- topic: introduction + path: /docs/Reference/Processors/TfsWorkItemMigrationProcessor-introduction.md + exists: false + markdown: '' + +--- \ No newline at end of file From c14f6253cfa3f7469210d24dec3e1b2f9b34842a Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 16:20:14 +0100 Subject: [PATCH 10/11] Added hidden config Builer option! --- appsettings.json | 4 +- configuration.json | 4 +- docs/Reference/Generated/MigrationTools.xml | 12 +- .../TfsTeamProjectAuthentication.cs | 3 + .../Properties/launchSettings.json | 8 +- .../Commands/ConfigurationBuilderCommand.cs | 130 ++++++++++++------ .../Commands/UpgradeConfigCommand.cs | 2 +- .../MigrationTools.Host.csproj | 1 + .../Infrastructure/DefaultOnlyConverter.cs | 36 +++++ .../Options/NetworkCredentialsOptions.cs | 4 + .../Options/OptionsConfigurationTemplates.cs | 10 +- .../Options/OptionsConfigurationUpgrader.cs | 56 ++++---- 12 files changed, 184 insertions(+), 86 deletions(-) create mode 100644 src/MigrationTools/Options/Infrastructure/DefaultOnlyConverter.cs diff --git a/appsettings.json b/appsettings.json index 1be2324a3..c5451e2e2 100644 --- a/appsettings.json +++ b/appsettings.json @@ -20,7 +20,7 @@ "AllowCrossProjectLinking": false, "Authentication": { "AuthenticationMode": "AccessToken", - "AccessToken": "12345", + "AccessToken": "", "NetworkCredentials": { "UserName": "", "Password": "", @@ -40,7 +40,7 @@ "AllowCrossProjectLinking": false, "Authentication": { "AuthenticationMode": "AccessToken", - "AccessToken": "12345", + "AccessToken": "", "NetworkCredentials": { "UserName": "", "Password": "", diff --git a/configuration.json b/configuration.json index 107f9c62d..d5d7fb942 100644 --- a/configuration.json +++ b/configuration.json @@ -1,6 +1,6 @@ { "Serilog": { - "MinimumLevel": "Debug" + "MinimumLevel": "Information" }, "MigrationTools": { "Version": "16.0", @@ -96,7 +96,7 @@ } ] }, - "GitRepoMappingTool": { + "TfsGitRepositoryTool": { "Enabled": true, "Mappings": { "Source Repo Name": "Target Repo Name" diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 56d79f75a..c0552d685 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -263,27 +263,27 @@ - => @"c18548ac" + => @"8bcfb8db" - => @"c18548acc45bff8d0ac85edde253ecda425d981d" + => @"8bcfb8dbb8d7ca68a38cbb8f521d7338a03d507a" - => @"2024-09-04T12:09:28+01:00" + => @"2024-09-04T13:17:54+01:00" - => @"7" + => @"9" - => @"v16.0.0-Preview.9-7-gc18548ac" + => @"v16.0.0-Preview.9-9-g8bcfb8db" @@ -318,7 +318,7 @@ - => @"7" + => @"9" diff --git a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs index fd47f02a9..a096c424d 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using MigrationTools.Options; +using MigrationTools.Options.Infrastructure; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -16,6 +17,8 @@ public class TfsAuthenticationOptions public AuthenticationMode AuthenticationMode { get; set; } public NetworkCredentials NetworkCredentials { get; set; } + + [JsonConverter(typeof(DefaultOnlyConverter), "** removed as a secret ***")] public string AccessToken { get; set; } } } diff --git a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json index 3ee5d2b4c..4b06ceb4e 100644 --- a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json +++ b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json @@ -19,10 +19,6 @@ "commandName": "Project", "commandLineArgs": "init --options Basic --overwrite" }, - "Config": { - "commandName": "Project", - "commandLineArgs": "config -c \"configuration.json\"" - }, "init Options-Reference": { "commandName": "Project", "commandLineArgs": "init --options Reference -c configuration-ref.json --overwrite" @@ -34,6 +30,10 @@ "Execute Classic": { "commandName": "Project", "commandLineArgs": "execute -c \"configuration-classic.json\"" + }, + "Builder with Config": { + "commandName": "Project", + "commandLineArgs": "builder -c \"configuration.json\"" } } } \ No newline at end of file diff --git a/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs index 7d41bdb94..973e62298 100644 --- a/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs +++ b/src/MigrationTools.Host/Commands/ConfigurationBuilderCommand.cs @@ -2,15 +2,18 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.VisualStudio.Services.Common; using MigrationTools.Options; using Newtonsoft.Json.Linq; using Spectre.Console; using Spectre.Console.Cli; +using Spectre.Console.Json; namespace MigrationTools.Host.Commands { @@ -33,6 +36,8 @@ public ConfigurationBuilderCommand( _appLifetime = appLifetime; } + Layout layout = new Layout("Root"); + public override async Task ExecuteAsync(CommandContext context, ConfigurationBuilderCommandSettings settings) { @@ -50,15 +55,16 @@ public override async Task ExecuteAsync(CommandContext context, Configurati // Load configuration OptionsConfigurationBuilder optionsBuilder = _services.GetRequiredService(); OptionsConfigurationUpgrader optionsUpgrader = _services.GetRequiredService(); - optionsUpgrader.UpgradeConfiguration(optionsBuilder); + optionsUpgrader.UpgradeConfiguration(optionsBuilder, configFile); + _logger.LogInformation("Configuration loaded & Upgraded to latest..."); + - // Dispay Current Options - DisplayCurrentOptions(); + var configurationEditorOptions = new[] { - "Templates", + "Apply Templates", "Save & Exit", "Exit" }; @@ -67,6 +73,9 @@ public override async Task ExecuteAsync(CommandContext context, Configurati bool shouldExit = false; while (!shouldExit) { + Console.Clear(); + DisplayCurrentOptions(optionsBuilder); + var selectedOption = AnsiConsole.Prompt( new SelectionPrompt() .Title("Select a configuration section to edit:") @@ -78,9 +87,10 @@ public override async Task ExecuteAsync(CommandContext context, Configurati switch (selectedOption) { case "Apply Templates": - SelectTemplateToApply(); + SelectTemplateToApply(optionsBuilder, configFile); break; case "Save & Exit": + SaveOptions(optionsBuilder, configFile); shouldExit = true; break; case "Exit": @@ -109,41 +119,69 @@ public override async Task ExecuteAsync(CommandContext context, Configurati return _exitCode; } - private void DisplayCurrentOptions() + private void DisplayCurrentOptions(OptionsConfigurationBuilder optionsBuilder) { - var layout = new Layout("Root") - .SplitColumns( - new Layout("Left"), - new Layout("Right") - .SplitRows( - new Layout("Top"), - new Layout("Bottom"))); - - // Update the left column - layout["Left"].Update( - new Panel( - Align.Center( - new Markup("Hello [blue]World![/]"), - VerticalAlignment.Middle)) - .Expand()); - - // Render the layout - AnsiConsole.Write(layout); + Console.Clear(); + AnsiConsole.WriteLine("Azure DevOps Migration Tools"); + AnsiConsole.Write( + new FigletText("Config Builder") + .LeftJustified() + .Color(Color.Purple)); + + + // Create the tree + AnsiConsole.WriteLine(); + AnsiConsole.Write(new Markup("[bold purple]Current Loadout[/]")); + AnsiConsole.WriteLine(); + AnsiConsole.WriteLine(); + var root = new Tree("MigrationTools"); + // Add some nodes + var endpoints = root.AddNode("Endpoints"); + foreach (var item in optionsBuilder.GetOptions(OptionItemType.Endpoint)) + { + endpoints.AddNode($"{item.Option.GetType().Name} ('{item.key}')"); + } + var processors = root.AddNode("Processors"); + foreach (var item in optionsBuilder.GetOptions(OptionItemType.Processor)) + { + processors.AddNode($"{item.Option.GetType().Name}"); + } + var tools = root.AddNode("Tools"); + foreach (var item in optionsBuilder.GetOptions(OptionItemType.Tool)) + { + tools.AddNode($"{item.Option.GetType().Name}"); + } + + // Render the tree + AnsiConsole.Write(root); } - private void SelectTemplateToApply() + private void SaveOptions(OptionsConfigurationBuilder optionsBuilder, string configFile) { - Console.Clear(); + string json = optionsBuilder.Build(); + if (File.Exists(configFile)) + { + File.Move(configFile, AddSuffixToFileName(configFile, $"-{DateTime.Now.ToString("yyyyMMddHHmmss")}")); + } + File.WriteAllText(configFile, json); + _logger.LogInformation("New {configFile} file has been created", configFile); + AnsiConsole.Write(new JsonText(json)); + } + + + private void SelectTemplateToApply(OptionsConfigurationBuilder optionsBuilder, string configFile) + { + bool shouldExit = false; while (!shouldExit) { - var options = new[] - { - "Work Item Migration", - "Save & Exit", - "Exit" - }; - options.AddRange(new[] { "Save & Exit", "Exit" }); + Console.Clear(); + DisplayCurrentOptions(optionsBuilder); + var options = new List(); + options.AddRange(Enum.GetNames(typeof(OptionsConfigurationTemplate))); + options.Add("Save & Exit"); + options.Add("Exit"); + var selectedOption = AnsiConsole.Prompt( new SelectionPrompt() .Title("Select a Template to apply to your config:") @@ -152,25 +190,39 @@ private void SelectTemplateToApply() switch (selectedOption) { - case "Work Item Migration": - ApplyTemplate("WorkItemMigration"); - break; case "Save & Exit": + SaveOptions(optionsBuilder, configFile); shouldExit = true; break; case "Exit": shouldExit = true; break; default: - Console.WriteLine($"Selected option: {selectedOption}"); + OptionsConfigurationTemplate tempopt = (OptionsConfigurationTemplate)Enum.Parse(typeof(OptionsConfigurationTemplate), selectedOption); + optionsBuilder.ApplyTemplate(tempopt); break; } } } - private void ApplyTemplate(string v) + + static string AddSuffixToFileName(string filePath, string suffix) { - throw new NotImplementedException(); + // Get the directory path + string directory = Path.GetDirectoryName(filePath); + + // Get the file name without the extension + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); + + // Get the file extension + string extension = Path.GetExtension(filePath); + + // Combine them to create the new file name + string newFileName = $"{fileNameWithoutExtension}{suffix}{extension}"; + + // Combine the directory with the new file name + return Path.Combine(directory, newFileName); } + } } diff --git a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs index 9818fe5c5..98504af5f 100644 --- a/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs +++ b/src/MigrationTools.Host/Commands/UpgradeConfigCommand.cs @@ -57,7 +57,7 @@ internal override async Task ExecuteInternalAsync(CommandContext context, U OptionsConfigurationBuilder optionsBuilder = Services.GetRequiredService(); OptionsConfigurationUpgrader optionsUpgrader = Services.GetRequiredService(); - optionsUpgrader.UpgradeConfiguration(optionsBuilder); + optionsUpgrader.UpgradeConfiguration(optionsBuilder, configFile); string json = optionsBuilder.Build(); configFile = AddSuffixToFileName(configFile, "-upgraded"); File.WriteAllText(configFile, json); diff --git a/src/MigrationTools.Host/MigrationTools.Host.csproj b/src/MigrationTools.Host/MigrationTools.Host.csproj index aa54a1cd1..66e3a4dba 100644 --- a/src/MigrationTools.Host/MigrationTools.Host.csproj +++ b/src/MigrationTools.Host/MigrationTools.Host.csproj @@ -34,6 +34,7 @@ + diff --git a/src/MigrationTools/Options/Infrastructure/DefaultOnlyConverter.cs b/src/MigrationTools/Options/Infrastructure/DefaultOnlyConverter.cs new file mode 100644 index 000000000..20246e50e --- /dev/null +++ b/src/MigrationTools/Options/Infrastructure/DefaultOnlyConverter.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using System; + +namespace MigrationTools.Options.Infrastructure +{ + + + public class DefaultOnlyConverter : JsonConverter + { + private readonly T _defaultValue; + + public DefaultOnlyConverter(T defaultValue) + { + _defaultValue = defaultValue; + } + + public override bool CanConvert(Type objectType) + { + return true; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // Always write the default value, no matter what the actual value is + writer.WriteValue(_defaultValue); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // Allow deserialization to work as normal + return serializer.Deserialize(reader, objectType); + } + } + + +} \ No newline at end of file diff --git a/src/MigrationTools/Options/NetworkCredentialsOptions.cs b/src/MigrationTools/Options/NetworkCredentialsOptions.cs index 3bb266246..8bd265a4e 100644 --- a/src/MigrationTools/Options/NetworkCredentialsOptions.cs +++ b/src/MigrationTools/Options/NetworkCredentialsOptions.cs @@ -1,4 +1,6 @@ using System; +using MigrationTools.Options.Infrastructure; +using Newtonsoft.Json; namespace MigrationTools.Options { @@ -13,6 +15,8 @@ public class NetworkCredentials { public string Domain { get; set; } public string UserName { get; set; } + + [JsonConverter(typeof(DefaultOnlyConverter), "** removed as a secret ***")] public string Password { get; set; } } } diff --git a/src/MigrationTools/Options/OptionsConfigurationTemplates.cs b/src/MigrationTools/Options/OptionsConfigurationTemplates.cs index e00fa9089..526765664 100644 --- a/src/MigrationTools/Options/OptionsConfigurationTemplates.cs +++ b/src/MigrationTools/Options/OptionsConfigurationTemplates.cs @@ -23,12 +23,6 @@ public static OptionsConfigurationBuilder ApplyTemplate(this OptionsConfiguratio optionsBuilder.AddAllOptions(); break; case OptionsConfigurationTemplate.Basic: - optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); - optionsBuilder.AddOption("FieldMappingTool"); - optionsBuilder.AddOption("FieldLiteralMap"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Source"); - optionsBuilder.AddOption("TfsTeamProjectEndpoint", "Target"); - break; case OptionsConfigurationTemplate.WorkItemTracking: optionsBuilder.AddOption("TfsWorkItemMigrationProcessor"); optionsBuilder.AddOption("FieldMappingTool"); @@ -38,8 +32,8 @@ public static OptionsConfigurationBuilder ApplyTemplate(this OptionsConfiguratio break; case OptionsConfigurationTemplate.PipelineProcessor: optionsBuilder.AddOption("AzureDevOpsPipelineProcessor"); - optionsBuilder.AddOption("AzureDevOpsEndpoint", "Source"); - optionsBuilder.AddOption("AzureDevOpsEndpoint", "Target"); + optionsBuilder.AddOption("AzureDevOpsEndpoint", "PipelineSource"); + optionsBuilder.AddOption("AzureDevOpsEndpoint", "PipelineTarget"); break; default: optionsBuilder.AddAllOptions(); diff --git a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs index 94e413af7..1e1bff5da 100644 --- a/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs +++ b/src/MigrationTools/Options/OptionsConfigurationUpgrader.cs @@ -30,11 +30,8 @@ namespace MigrationTools.Options public class OptionsConfigurationUpgrader { public IServiceProvider Services { get; } - public IConfiguration Configuration { get; } readonly ILogger _logger; - - private static Dictionary classNameChangeLog = new Dictionary(); private List catalogue; @@ -45,7 +42,6 @@ public OptionsConfigurationUpgrader( ITelemetryLogger telemetryLogger, IServiceProvider services) { this.Services = services; - this.Configuration = configuration; this._logger = logger; catalogue = AppDomain.CurrentDomain.GetMigrationToolsTypes().WithInterface().ToList(); configuration.GetSection("MigrationTools:Infrastructure:ClassNameChangeMappings").Bind(classNameChangeLog); @@ -55,7 +51,7 @@ public OptionsConfigurationUpgrader( } } - public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuilder optionsBuilder) + public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuilder optionsBuilder, string configurationFile) { using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("OptionsConfigurationUpgrader::UpgradeConfiguration")) { @@ -64,7 +60,19 @@ public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuil optionsBuilder = this.Services.GetService(); } - var schemaVersion = VersionOptions.ConfigureOptions.GetMigrationConfigVersion(Configuration); + if (string.IsNullOrEmpty(configurationFile)) + { + configurationFile = "configuration.json"; + } + _logger.LogInformation("Importing: {configFile}", configurationFile); + + // Load configuration + var configuration = new ConfigurationBuilder() + .AddJsonFile(configurationFile, optional: false, reloadOnChange: false) + .Build(); + + + var schemaVersion = VersionOptions.ConfigureOptions.GetMigrationConfigVersion(configuration); activity?.AddTag("SchemaVersion", schemaVersion.schema.ToString()); activity.AddEvent(new ActivityEvent($"UpgradeConfigCommand.{schemaVersion.schema.ToString()}")); switch (schemaVersion.schema) @@ -73,29 +81,29 @@ public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuil case MigrationConfigSchema.v150: activity.AddEvent(new ActivityEvent("UpgradeConfigCommand.v150")); // ChangeSetMappingFile - optionsBuilder.AddOption(ParseV1TfsChangeSetMappingToolOptions(Configuration)); - optionsBuilder.AddOption(ParseV1TfsGitRepoMappingOptions(Configuration)); - optionsBuilder.AddOption(ParseV1FieldMaps(Configuration)); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "Processors", "$type")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "CommonEnrichersConfig", "$type")); - if (!IsSectionNullOrEmpty(Configuration.GetSection("Source")) || !IsSectionNullOrEmpty(Configuration.GetSection("Target"))) + optionsBuilder.AddOption(ParseV1TfsChangeSetMappingToolOptions(configuration)); + optionsBuilder.AddOption(ParseV1TfsGitRepoMappingOptions(configuration)); + optionsBuilder.AddOption(ParseV1FieldMaps(configuration)); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(configuration, "Processors", "$type")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(configuration, "CommonEnrichersConfig", "$type")); + if (!IsSectionNullOrEmpty(configuration.GetSection("Source")) || !IsSectionNullOrEmpty(configuration.GetSection("Target"))) { - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Source", "$type"), "Source"); - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "Target", "$type"), "Target"); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(configuration, "Source", "$type"), "Source"); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(configuration, "Target", "$type"), "Target"); } else { - optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Source"), "Source"); - optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(Configuration, "Endpoints:AzureDevOpsEndpoints", "Target"), "Target"); + optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(configuration, "Endpoints:AzureDevOpsEndpoints", "Source"), "Source"); + optionsBuilder.AddOption(ParseSectionCollectionWithPathAsTypeToOption(configuration, "Endpoints:AzureDevOpsEndpoints", "Target"), "Target"); } break; case MigrationConfigSchema.v160: - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Source", "EndpointType"), "Source"); - optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(Configuration, "MigrationTools:Endpoints:Target", "EndpointType"), "Target"); - optionsBuilder.AddOption(ParseSectionListWithPathAsTypeToOption(Configuration, "MigrationTools:CommonTools")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:CommonTools:FieldMappingTool:FieldMaps", "FieldMapType")); - optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(Configuration, "MigrationTools:Processors", "ProcessorType")); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(configuration, "MigrationTools:Endpoints:Source", "EndpointType"), "Source"); + optionsBuilder.AddOption(ParseSectionWithTypePropertyNameToOptions(configuration, "MigrationTools:Endpoints:Target", "EndpointType"), "Target"); + optionsBuilder.AddOption(ParseSectionListWithPathAsTypeToOption(configuration, "MigrationTools:CommonTools")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(configuration, "MigrationTools:CommonTools:FieldMappingTool:FieldMaps", "FieldMapType")); + optionsBuilder.AddOption(ParseSectionCollectionWithTypePropertyNameToList(configuration, "MigrationTools:Processors", "ProcessorType")); break; } return optionsBuilder; @@ -142,7 +150,7 @@ private List ParseSectionCollectionWithTypePropertyNameToList(IConfigu { var optionTypeString = childSection.GetValue(typePropertyName); var newOptionTypeString = ParseOptionsType(optionTypeString); - _logger.LogInformation("Upgrading {group} item {old} to {new}", path, optionTypeString, newOptionTypeString); + _logger.LogDebug("Upgrading {group} item {old} to {new}", path, optionTypeString, newOptionTypeString); var option = GetOptionWithDefaults(configuration, newOptionTypeString); childSection.Bind(option); options.Add(option); @@ -154,7 +162,7 @@ private List ParseSectionCollectionWithTypePropertyNameToList(IConfigu private List ParseV1FieldMaps(IConfiguration configuration) { List options = new List(); - _logger.LogInformation("Upgrading {old} to {new}", "FieldMaps", "FieldMappingToolOptions"); + _logger.LogDebug("Upgrading {old} to {new}", "FieldMaps", "FieldMappingToolOptions"); var toolOption = GetOptionWithDefaults(configuration, ParseOptionsType("FieldMappingToolOptions")); toolOption.Enabled = true; options.Add(toolOption); @@ -175,7 +183,7 @@ private IOptions ParseSectionWithTypePropertyNameToOptions(IConfiguration config private IOptions GetOptionFromTypeString(IConfiguration configuration, IConfigurationSection optionsConfig, string optionTypeString) { var newOptionTypeString = ParseOptionsType(optionTypeString); - _logger.LogInformation("Upgrading to {old} to {new}", optionTypeString, newOptionTypeString); + _logger.LogDebug("Upgrading to {old} to {new}", optionTypeString, newOptionTypeString); IOptions sourceOptions; sourceOptions = GetOptionWithDefaults(configuration, newOptionTypeString); optionsConfig.Bind(sourceOptions); From 570c069225602faa67d4d38f259ad57bd27a3245 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Wed, 4 Sep 2024 16:31:04 +0100 Subject: [PATCH 11/11] + fix for small --disableTelemetry issue! --- .../Properties/launchSettings.json | 2 +- .../Commands/CommandBase.cs | 143 +++++++++--------- 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json index 4b06ceb4e..321faa533 100644 --- a/src/MigrationTools.ConsoleFull/Properties/launchSettings.json +++ b/src/MigrationTools.ConsoleFull/Properties/launchSettings.json @@ -9,7 +9,7 @@ }, "execute": { "commandName": "Project", - "commandLineArgs": "execute -c \"configuration.json\" --debugTrace" + "commandLineArgs": "execute -c \"configuration.json\" --disableTelemetry --debugTrace" }, "execute --help": { "commandName": "Project", diff --git a/src/MigrationTools.Host/Commands/CommandBase.cs b/src/MigrationTools.Host/Commands/CommandBase.cs index 373d7535c..13d8fc1e3 100644 --- a/src/MigrationTools.Host/Commands/CommandBase.cs +++ b/src/MigrationTools.Host/Commands/CommandBase.cs @@ -57,33 +57,32 @@ public CommandBase( Services = services; Configuration = configuration; this.ActivitySource = activitySource; + } public Activity CommandActivity { get; private set; } public sealed override async Task ExecuteAsync(CommandContext context, TSettings settings) { - // Disable Telemetry - if (settings.DisableTelemetry) - { - Log.Debug("Disabling Telemetry {CommandName}", this.GetType().Name); - CommandActivity.AddTag("DisableTelemetry", settings.DisableTelemetry); - CommandActivity.Stop(); - ActivitySourceProvider.DisableActivitySource(); - } - //Enable Debug Trace - if (settings.DebugTrace) - { - Log.Debug("Enabling Telemetry DebugTrace {CommandName}", this.GetType().Name); - ActivitySourceProvider.EnableTelemeteryDebug(); - } - using (CommandActivity = ActivitySource.StartActivity(this.GetType().Name)) { CommandActivity.SetTagsFromObject(settings); CommandActivity?.Start(); - - + // Disable Telemetry + if (settings.DisableTelemetry) + { + Log.Debug("Disabling Telemetry {CommandName}", this.GetType().Name); + CommandActivity.AddTag("DisableTelemetry", settings.DisableTelemetry); + CommandActivity.Stop(); + ActivitySourceProvider.DisableActivitySource(); + } + //Enable Debug Trace + if (settings.DebugTrace) + { + Log.Debug("Enabling Telemetry DebugTrace {CommandName}", this.GetType().Name); + ActivitySourceProvider.EnableTelemeteryDebug(); + } + // Run the command Log.Verbose("Starting {CommandName}", this.GetType().Name); CommandActivity.AddEvent(new ActivityEvent("Starting")); @@ -119,73 +118,73 @@ internal virtual async Task ExecuteInternalAsync(CommandContext context, TS public void RunStartupLogic(TSettings settings) { - ApplicationStartup(settings); - if (!settings.skipVersionCheck && _detectOnlineService.IsOnline()) + ApplicationStartup(settings); + if (!settings.skipVersionCheck && _detectOnlineService.IsOnline()) + { + _logger.LogTrace("Package Management Info:"); + Log.Debug(" IsPackageManagerInstalled: {IsPackageManagerInstalled}", _detectVersionService.IsPackageManagerInstalled); + Log.Debug(" IsPackageInstalled: {IsPackageInstalled}", _detectVersionService.IsPackageInstalled); + Log.Debug(" IsUpdateAvailable: {IsUpdateAvailable}", _detectVersionService.IsUpdateAvailable); + Log.Debug(" IsNewLocalVersionAvailable: {IsNewLocalVersionAvailable}", _detectVersionService.IsNewLocalVersionAvailable); + Log.Debug(" IsRunningInDebug: {IsRunningInDebug}", _detectVersionService.IsRunningInDebug); + Log.Verbose("Full version data: ${_detectVersionService}", _detectVersionService); + + Log.Information("Verion Info:"); + Log.Information(" Running: {RunningVersion}", _detectVersionService.RunningVersion); + Log.Information(" Installed: {InstalledVersion}", _detectVersionService.InstalledVersion); + Log.Information(" Available: {AvailableVersion}", _detectVersionService.AvailableVersion); + + if (_detectVersionService.RunningVersion.Major == 0) { - _logger.LogTrace("Package Management Info:"); - Log.Debug(" IsPackageManagerInstalled: {IsPackageManagerInstalled}", _detectVersionService.IsPackageManagerInstalled); - Log.Debug(" IsPackageInstalled: {IsPackageInstalled}", _detectVersionService.IsPackageInstalled); - Log.Debug(" IsUpdateAvailable: {IsUpdateAvailable}", _detectVersionService.IsUpdateAvailable); - Log.Debug(" IsNewLocalVersionAvailable: {IsNewLocalVersionAvailable}", _detectVersionService.IsNewLocalVersionAvailable); - Log.Debug(" IsRunningInDebug: {IsRunningInDebug}", _detectVersionService.IsRunningInDebug); - Log.Verbose("Full version data: ${_detectVersionService}", _detectVersionService); - - Log.Information("Verion Info:"); - Log.Information(" Running: {RunningVersion}", _detectVersionService.RunningVersion); - Log.Information(" Installed: {InstalledVersion}", _detectVersionService.InstalledVersion); - Log.Information(" Available: {AvailableVersion}", _detectVersionService.AvailableVersion); - - if (_detectVersionService.RunningVersion.Major == 0) - { - Log.Information("Git Info:"); - Log.Information(" Repo: {GitRepositoryUrl}", ThisAssembly.Git.RepositoryUrl); - Log.Information(" Tag: {GitTag}", ThisAssembly.Git.Tag); - Log.Information(" Branch: {GitBranch}", ThisAssembly.Git.Branch); - Log.Information(" Commits: {GitCommits}", ThisAssembly.Git.Commits); + Log.Information("Git Info:"); + Log.Information(" Repo: {GitRepositoryUrl}", ThisAssembly.Git.RepositoryUrl); + Log.Information(" Tag: {GitTag}", ThisAssembly.Git.Tag); + Log.Information(" Branch: {GitBranch}", ThisAssembly.Git.Branch); + Log.Information(" Commits: {GitCommits}", ThisAssembly.Git.Commits); - } + } - if (!_detectVersionService.IsPackageManagerInstalled) - { - Log.Warning("Windows Client: The Windows Package Manager is not installed, we use it to determine if you have the latest version, and to make sure that this application is up to date. You can download and install it from https://aka.ms/getwinget. After which you can call `winget install {PackageId}` from the Windows Terminal to get a manged version of this program.", _detectVersionService.PackageId); - Log.Warning("Windows Server: If you are running on Windows Server you can use the experimental version of Winget, or you can still use Chocolatey to manage the install. Install chocolatey from https://chocolatey.org/install and then use `choco install vsts-sync-migrator` to install, and `choco upgrade vsts-sync-migrator` to upgrade to newer versions.", _detectVersionService.PackageId); - } - else + if (!_detectVersionService.IsPackageManagerInstalled) + { + Log.Warning("Windows Client: The Windows Package Manager is not installed, we use it to determine if you have the latest version, and to make sure that this application is up to date. You can download and install it from https://aka.ms/getwinget. After which you can call `winget install {PackageId}` from the Windows Terminal to get a manged version of this program.", _detectVersionService.PackageId); + Log.Warning("Windows Server: If you are running on Windows Server you can use the experimental version of Winget, or you can still use Chocolatey to manage the install. Install chocolatey from https://chocolatey.org/install and then use `choco install vsts-sync-migrator` to install, and `choco upgrade vsts-sync-migrator` to upgrade to newer versions.", _detectVersionService.PackageId); + } + else + { + if (!_detectVersionService.IsRunningInDebug) { - if (!_detectVersionService.IsRunningInDebug) + if (!_detectVersionService.IsPackageInstalled) { - if (!_detectVersionService.IsPackageInstalled) + Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version?"); + Console.WriteLine("Do you want exit and install the managed version? (y/n)"); + if (Console.ReadKey().Key == ConsoleKey.Y) { - Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version?"); - Console.WriteLine("Do you want exit and install the managed version? (y/n)"); - if (Console.ReadKey().Key == ConsoleKey.Y) - { - Thread.Sleep(2000); - Environment.Exit(0); - } - } - if (_detectVersionService.IsUpdateAvailable && _detectVersionService.IsPackageInstalled) - { - Log.Information("It looks like an updated version is available from Winget, would you like to exit and update?"); - Console.WriteLine("Do you want to exit and update? (y/n)"); - if (Console.ReadKey().Key == ConsoleKey.Y) - { - Thread.Sleep(2000); - Environment.Exit(0); - } + Thread.Sleep(2000); + Environment.Exit(0); } } - else + if (_detectVersionService.IsUpdateAvailable && _detectVersionService.IsPackageInstalled) { - Log.Information("Running in Debug! No further version checkes....."); + Log.Information("It looks like an updated version is available from Winget, would you like to exit and update?"); + Console.WriteLine("Do you want to exit and update? (y/n)"); + if (Console.ReadKey().Key == ConsoleKey.Y) + { + Thread.Sleep(2000); + Environment.Exit(0); + } } } + else + { + Log.Information("Running in Debug! No further version checkes....."); + } } - else - { - /// not online or you have specified not to - Log.Warning("You are either not online or have chosen `skipVersionCheck`. We will not check for a newer version of the tools.", _detectVersionService.PackageId); - } + } + else + { + /// not online or you have specified not to + Log.Warning("You are either not online or have chosen `skipVersionCheck`. We will not check for a newer version of the tools.", _detectVersionService.PackageId); + } }