From ff39badd0ec21dee6660cd603c436abf1bac5ed9 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Tue, 30 Jan 2024 10:04:01 +0000 Subject: [PATCH 1/6] Preview: Refactor/tfs node structure options (#1886) This update is a +semver: major change. There are many config changes! **Change 1:** Updates all Processors to use only `CommonEnrichersConfig` and removes inline options. _Only options set in `CommonEnrichersConfig` will be honored._ This will minimise the number of configuration options for processors, and reduce confusion and support tickets. 1. `TfsNodeStructureOptions` has been made the default so `AreaMaps`, `IterationMaps`, `NodeBasePaths`, `PrefixProjectToNodes`, `ShouldCreateMissingRevisionPaths`, and `ReplicateAllExistingNodes` are all removed from `WorkItemMigrationContext` and must instead be added to `CommonEnrichersConfig` in line with [TfsNodeStructure](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v2/ProcessorEnrichers/TfsNodeStructure/). The documentation for `AreaMaps`, `IterationMaps`, `NodeBasePaths` has all been moved here. 2. `TfsRevisionManagerOptions` has been made default so `ReplayRevisions` and `MaxRevisions` is now only configurable by adding a `TfsRevisionManagerOptions` section to `CommonEnrichersConfig`. See [TfsRevisionManagerOptions](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v2/ProcessorEnrichers/TfsRevisionManager/) 3. 'TfsWorkItemLinkEnricherOptions' has been made the default moving its option `FilterIfLinkCountMatches`, and `SaveAfterEachLinkIsAdded` to `CommonEnrichersConfig`. See [TfsWorkItemLinkEnricherOptions](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v2/ProcessorEnrichers/TfsWorkItemLinkEnricher/) ``` "CommonEnrichersConfig": [ { "$type": "TfsNodeStructureOptions", "PrefixProjectToNodes": false, "NodeBasePaths": [], "AreaMaps": { "^Skypoint Cloud$": "MigrationTest5" }, "IterationMaps": { "^Skypoint Cloud\\\\Sprint 1$": "MigrationTest5\\Sprint 1" }, "ShouldCreateMissingRevisionPaths": true, "ReplicateAllExistingNodes": true }, { "$type": "TfsWorkItemLinkEnricherOptions", "Enabled": true, "FilterIfLinkCountMatches": true, "SaveAfterEachLinkIsAdded": false }, { "$type": "TfsRevisionManagerOptions", "Enabled": true, "ReplayRevisions": true, "MaxRevisions": 0 } ], ``` **Change 2:** Update the Query system to no longer have two parts that are added together. All queries are now the full query. There are no docs on this but a typical query now looks like: ``` "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", ``` --- configuration.json | 113 +++---- ...nTools.Clients.AzureDevops.ObjectModel.xml | 65 ++++ docs/Reference/Generated/MigrationTools.xml | 121 +------ .../WorkItemMigrationContext-notes.md | 187 +--------- docs/Reference/v1/index.md | 2 +- .../TfsNodeStructure-introduction.md | 0 .../TfsNodeStructure-notes.md | 206 +++++++++++ ...rence.v1.processors.fixgitcommitlinks.yaml | 9 +- ...rs.testplansandsuitesmigrationcontext.yaml | 29 +- ...eference.v1.processors.workitemdelete.yaml | 9 +- ...1.processors.workitemmigrationcontext.yaml | 46 +-- ...cessors.workitempostprocessingcontext.yaml | 9 +- ...eference.v1.processors.workitemupdate.yaml | 9 +- ...rs.appendmigrationtoolsignaturefooter.yaml | 4 +- ...lterworkitemsthatalreadyexistintarget.yaml | 4 +- ...processorenrichers.pauseaftereachitem.yaml | 4 +- ...ichers.skiptofinalrevisedworkitemtype.yaml | 4 +- ...2.processorenrichers.tfsnodestructure.yaml | 24 +- ...processorenrichers.tfsrevisionmanager.yaml | 16 +- ...sorenrichers.tfsvalidaterequiredfield.yaml | 4 +- ...ssorenrichers.tfsworkitemlinkenricher.yaml | 38 ++- .../sampleConfig/configration-demo-v15.0.json | 102 ++++++ ...ference.v1.processors.fixgitcommitlinks.md | 9 +- ...sors.testplansandsuitesmigrationcontext.md | 29 +- .../reference.v1.processors.workitemdelete.md | 9 +- ....v1.processors.workitemmigrationcontext.md | 309 +---------------- ...rocessors.workitempostprocessingcontext.md | 9 +- .../reference.v1.processors.workitemupdate.md | 9 +- ...hers.appendmigrationtoolsignaturefooter.md | 4 +- ...filterworkitemsthatalreadyexistintarget.md | 4 +- ...2.processorenrichers.pauseaftereachitem.md | 4 +- ...nrichers.skiptofinalrevisedworkitemtype.md | 4 +- ....v2.processorenrichers.tfsnodestructure.md | 319 +++++++++++++++++- ...2.processorenrichers.tfsrevisionmanager.md | 16 +- ...essorenrichers.tfsvalidaterequiredfield.md | 4 +- ...cessorenrichers.tfsworkitemlinkenricher.md | 38 ++- .../ProcessorEnrichers/TfsNodeStructure.cs | 3 + .../TfsNodeStructureOptions.cs | 35 ++ .../ProcessorEnrichers/TfsRevisionManager.cs | 5 +- .../TfsRevisionManagerOptions.cs | 20 ++ .../TfsWorkItemLinkEnricher.cs | 47 ++- .../TfsWorkItemLinkEnricherOptions.cs | 43 +++ .../_Enginev1/Clients/TfsWiqlDefinition.cs | 8 - .../Clients/TfsWorkItemMigrationClient.cs | 13 +- .../KeepOutboundLinkTargetProcessor.cs | 6 +- .../KeepOutboundLinkTargetProcessorOptions.cs | 4 +- .../OutboundLinkCheckingProcessor.cs | 6 +- .../OutboundLinkCheckingProcessorOptions.cs | 4 +- src/MigrationTools.Samples/configuration.json | 6 +- .../demo-mapping-scrum2Agile.json | 119 +++---- .../demo-migration-reset.json | 2 +- .../demo-migration.json | 20 +- .../ProcessorEnricherOptions.cs | 7 + .../EngineConfigurationBuilder.cs | 1 + .../Configuration/IWorkItemProcessorConfig.cs | 4 +- .../Processing/FixGitCommitLinksConfig.cs | 5 +- .../TestPlansAndSuitesMigrationConfig.cs | 27 +- .../Processing/WorkItemDeleteConfig.cs | 6 +- .../Processing/WorkItemMigrationConfig.cs | 79 +---- .../WorkItemPostProcessingConfig.cs | 9 +- .../Processing/WorkItemUpdateConfig.cs | 9 +- .../WorkItemMigrationTests.cs | 51 ++- .../ComponentContext/TestManagementContext.cs | 10 +- .../TestPlansAndSuitesMigrationContext.cs | 23 +- .../WorkItemMigrationContext.cs | 95 +++--- .../WorkItemPostProcessingContext.cs | 37 +- .../ProcessingContext/FixGitCommitLinks.cs | 8 +- .../ProcessingContext/WorkItemDelete.cs | 6 +- .../ProcessingContext/WorkItemUpdate.cs | 3 +- 69 files changed, 1219 insertions(+), 1274 deletions(-) create mode 100644 docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-introduction.md create mode 100644 docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md create mode 100644 docs/_includes/sampleConfig/configration-demo-v15.0.json rename src/MigrationTools.Clients.AzureDevops.ObjectModel/{Enrichers => ProcessorEnrichers}/TfsWorkItemLinkEnricher.cs (93%) create mode 100644 src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs delete mode 100644 src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWiqlDefinition.cs diff --git a/configuration.json b/configuration.json index 625869171..5ac9297a6 100644 --- a/configuration.json +++ b/configuration.json @@ -1,15 +1,14 @@ { - "Version": "0.0", - "LogLevel": "Verbose", - "workaroundForQuerySOAPBugEnabled": false, + "ChangeSetMappingFile": null, "Source": { "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", "Project": "migrationSource1", - "ReflectedWorkItemIDFieldName": "Custom.ReflectedWorkItemId", - "AuthenticationMode": "Prompt", + "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", "AllowCrossProjectLinking": false, - "PersonalAccessToken": "rrsne75npwj5ctn5vm337nrxiqlvdkfmcbkqrubl6ushts6syi5a", + "AuthenticationMode": "Prompt", + "PersonalAccessToken": "", + "PersonalAccessTokenVariableName": "", "LanguageMaps": { "AreaPath": "Area", "IterationPath": "Iteration" @@ -18,68 +17,54 @@ "Target": { "$type": "TfsTeamProjectConfig", "Collection": "https://dev.azure.com/nkdagility-preview/", - "Project": "migration Target 1", + "Project": "migrationTest5", "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", - "AuthenticationMode": "Prompt", "AllowCrossProjectLinking": false, - "PersonalAccessToken": "rrsne75npwj5ctn5vm337nrxiqlvdkfmcbkqrubl6ushts6syi5a", + "AuthenticationMode": "Prompt", + "PersonalAccessToken": "njp3kcec4nbev63fmbepvdpn35drawmonk5qf5yqsw77dgfwnjda", + "PersonalAccessTokenVariableName": "", "LanguageMaps": { "AreaPath": "Area", "IterationPath": "Iteration" } }, - "FieldMaps": [ - { - "$type": "TreeToTagMapConfig", - "WorkItemTypeName": "*", - "toSkip": 3, - "timeTravel": 1 - } - ], - "WorkItemTypeDefinition": { - "sourceWorkItemTypeName": "targetWorkItemTypeName" - }, + "FieldMaps": [], "GitRepoMapping": null, + "LogLevel": "Debug", "CommonEnrichersConfig": [ { "$type": "TfsNodeStructureOptions", "PrefixProjectToNodes": false, "NodeBasePaths": [], - "AreaMaps": {}, - "IterationMaps": {}, - "ShouldCreateMissingRevisionPaths": true - } - ], - "Processors": [ + "AreaMaps": { + "^Skypoint Cloud$": "MigrationTest5" + }, + "IterationMaps": { + "^Skypoint Cloud\\\\Sprint 1$": "MigrationTest5\\Sprint 1" + }, + "ShouldCreateMissingRevisionPaths": true, + "ReplicateAllExistingNodes": true + }, { - "$type": "ExportUsersForMappingConfig", - "Enabled": false, - "LocalExportJsonFile": "c:\\temp\\ExportUsersForMappingConfig.json", - "WIQLQuery": "SELECT [System.Id], [System.Tags] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') ORDER BY [System.ChangedDate] desc", - "IdentityFieldsToCheck": [ - "System.AssignedTo", - "System.ChangedBy", - "System.CreatedBy", - "Microsoft.VSTS.Common.ActivatedBy", - "Microsoft.VSTS.Common.ResolvedBy", - "Microsoft.VSTS.Common.ClosedBy" - ] + "$type": "TfsWorkItemLinkEnricherOptions", + "Enabled": true, + "FilterIfLinkCountMatches": true, + "SaveAfterEachLinkIsAdded": false }, - //{ - // "$type": "WorkItemDeleteConfig", - // "Enabled": true, - // "WIQLQueryBit": "AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - // "WIQLOrderBit": "[System.ChangedDate] desc" - //}, { - "$type": "WorkItemMigrationConfig", + "$type": "TfsRevisionManagerOptions", "Enabled": true, "ReplayRevisions": true, - "PrefixProjectToNodes": false, + "MaxRevisions": 0 + } + ], + "Processors": [ + { + "$type": "WorkItemMigrationConfig", + "Enabled": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, - "WIQLQueryBit": "AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - "WIQLOrderBit": "[System.ChangedDate] desc", + "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", "LinkMigration": true, "AttachmentMigration": true, "AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\", @@ -93,16 +78,26 @@ "LinkMigrationSaveEachAsAdded": false, "GenerateMigrationComment": true, "WorkItemIDs": null, - "MaxRevisions": 0, - "NodeStructureEnricherEnabled": true, - "UseCommonNodeStructureEnricherConfig": false, - "ShouldCreateMissingRevisionPaths": false, - "NodeBasePaths": [], - "AreaMaps": {}, - "IterationMaps": { - }, - "MaxGracefulFailures": 0 + "MaxGracefulFailures": 0, + "SkipRevisionWithInvalidIterationPath": false, + "SkipRevisionWithInvalidAreaPath": false } - - ] + ], + "Version": "14.2", + "workaroundForQuerySOAPBugEnabled": false, + "WorkItemTypeDefinition": { + "sourceWorkItemTypeName": "targetWorkItemTypeName" + }, + "Endpoints": { + "InMemoryWorkItemEndpoints": [ + { + "Name": "Source", + "EndpointEnrichers": null + }, + { + "Name": "Target", + "EndpointEnrichers": null + } + ] + } } \ No newline at end of file diff --git a/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml b/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml index af635eeea..228550996 100644 --- a/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml +++ b/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml @@ -13,6 +13,11 @@ from https://gist.github.com/pietergheysens/792ed505f09557e77ddfc1b83531e4fb + + + The TfsNodeStructureEnricher is used to create missing nodes in the target project. To configure it add a `TfsNodeStructureOptions` section to `CommonEnrichersConfig` in the config file. Otherwise defaults will be applied. + + Checks node-to-be-created with allowed BasePath's @@ -28,6 +33,66 @@ A boolean indicating whether the path is a parent of any positively selected base path. + + + Prefix the nodes with the new project name. + + false + + + + The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) + + ["/"] + + + + Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, + that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + + {} + + + + Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, + that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + + {} + + + + When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. + + + + + The TfsRevisionManager manipulates the revisions of a work item to reduce the number of revisions that are migrated. + + + + + You can choose to migrate the tip only (a single write) or all of the revisions (many writes). + If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. + + true + + + + Sets the maximum number of revisions that will be migrated. "First + Last N = Max". + If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. + + 0 + + + + Skip validating links if the number of links in the source and the target matches! + + + + + Save the work item after each link is added. This will slow the migration as it will cause many saves to the TFS database. + + The `TfsAreaAndIterationProcessor` migrates all of the Area nd Iteraion paths. diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 82e09c1b0..483f29a40 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -45,6 +45,16 @@ Active the enricher if it true. + + + For internal use + + + + + For internal use + + If you set a `RefName` then this configration will be added to a Catalog of configurations that can be refernced using tha `RefName` so tha tyou dont have to keep adding the ame items with the same configuration. @@ -181,9 +191,6 @@ List of already configured processors. - - - The source domain where the pictures should be exported. @@ -229,9 +236,6 @@ - - - @@ -280,19 +284,13 @@ - - - Prefix the nodes with the new project name. - - false - The tag name that is present on all elements that must be migrated. If this option isn't present this processor will migrate all. `String.Empty` - + Filtering conditions to decide whether to migrate a test plan or not. When provided, this partial query is added after `Select * From TestPlan Where` when selecting test plans. Among filtering options, `AreaPath`, `PlanName` and `PlanState` are known to work. There is unfortunately no documentation regarding the available fields. @@ -310,30 +308,12 @@ 0 - + Indicates whether the configuration for node structure transformation should be taken from the common enricher configs. Otherwise the configuration elements below are used false - - - See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - - [] - - - - See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - - null - - - - See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - - null - Remove Invalid Links, see https://github.com/nkdAgility/azure-devops-migration-tools/issues/178 @@ -354,19 +334,6 @@ - - - You can choose to migrate the tip only (a single write) or all of the revisions (many writes). - If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. - - true - - - - Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - - false - If this is enabled the creation process on the target project will create the items with the original creation date. @@ -383,17 +350,11 @@ true - + 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) - AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - - - - A work item query to affect the order in which the work items are migrated. Don't leave this empty. - - [System.ChangedDate] desc + 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 @@ -492,39 +453,6 @@ [] - - - Sets the maximum number of revisions that will be migrated. "First + Last N = Max". - If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. - - 0 - - - - - - ? - - - - The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) - - ["/"] - - - - Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, - that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - - {} - - - - Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, - that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - - {} - 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 @@ -542,11 +470,6 @@ 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. - - - When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. - - @@ -567,18 +490,12 @@ - + 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) AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - - - A work item query to affect the order in which the work items are migrated. Don't leave this empty. - - [System.ChangedDate] desc - 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. @@ -650,18 +567,12 @@ - + 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) AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - - - A work item query to affect the order in which the work items are migrated. Don't leave this empty. - - [System.ChangedDate] desc - A list of work items to import diff --git a/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md b/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md index 4bf481e92..af461fb76 100644 --- a/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md +++ b/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md @@ -1,3 +1,4 @@ + ## WIQL Query Bits The Work Item queries are all built using Work Item [Query Language (WIQL)](https://docs.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax). @@ -39,191 +40,13 @@ Scope to Area Path (Team data): "WIQLOrderBit": "[System.ChangedDate] desc", ``` -## NodeBasePath Configuration ## -The `NodeBasePaths` entry allows the filtering of the nodes to be replicated on the target projects. To try to explain the correct usage let us assume that we have a source team project `SourceProj` with the following node structures - -- AreaPath - - SourceProj - - SourceProj\Team 1 - - SourceProj\Team 2 - - SourceProj\Team 2\Sub-Area - - SourceProj\Team 3 -- IterationPath - - SourceProj - - SourceProj\Sprint 1 - - SourceProj\Sprint 2 - - SourceProj\Sprint 2\Sub-Iteration - - SourceProj\Sprint 3 - -Depending upon what node structures you wish to migrate you would need the following settings. Exclusions are also possible by prefixing a path with an exclamation mark `!`. Example are - -| | | -|-|-| -| Intention | Migrate all areas and iterations and all Work Items -| NodeBasePath | `[]` -| Comment | The same AreaPath and Iteration Paths are created on the target as on the source. Hence, all migrated WI remain in their existing area and iteration paths -|| -| Intention | Only migrate area path `Team 2` and it associated Work Items, but all iteration paths -| NodeBasePath | `["Team 2", "Sprint"]` -| Comment | Only the area path ending `Team 2` will be migrated.
The `WIQLQueryBit` should be edited to limit the WI migrated to this area path e.g. add `AND [System.AreaPath] UNDER 'SampleProject\\Team 2'` .
The migrated WI will have an area path of `TargetProj\Team 2` but retain their iteration paths matching the sprint name on the source -|| -| Intention | Only migrate iterations structure -| NodeBasePath | `["Sprint"]` -| Comment | Only the area path ending `Team 2` will be migrated
All the iteration paths will be migrated.
The migrated WI will have the default area path of `TargetProj` as their source area path was not migrated i.e. `TargetProj`
The migrated WI will have an iteration path match the sprint name on the source -|| -| Intention | Move all WI to the existing area and iteration paths on the targetProj -| NodeBasePath | `["DUMMY VALUE"]` -| Comment | As the `NodeBasePath` does not match any source area or iteration path no nodes are migrated.
Migrated WI will be assigned to any matching area or iteration paths. If no matching ones can be found they will default to the respective root values -|| -| Intention | Move the `Team 2` area, but not its `Sub-Area` -| NodeBasePath | `["Team 2", "!Team 2\\SubArea"]` -| Comment | The Work Items will have to be restricted to the right areas, e.g. with `AND [System.AreaPath] UNDER 'SampleProject\\Team 2' AND [System.AreaPath] NOT UNDER 'SampleProject\\Team 2\\Sub-Area'`, otherwise their migratin will fail - -# Iteration Maps and Area Maps - -These two configuration elements apply after the `NodeBasePaths` selector, i.e. -only on Areas and Iterations that have been selected for migration. They allow -to change the area path, respectively the iteration path, of migrated work items. - -These remapping rules are applied both while creating path nodes in the target -project and when migrating work items. - -These remapping rules are applied with a higher priority than the -`PrefixProjectToNodes` option. This means that if no declared rule matches the -path and the `PrefixProjectToNodes` option is enabled, then the old behavior is -used. - -The syntax is a dictionary of regular expressions and the replacement text. - -*Warning*: These follow the -[.net regular expression language](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference). -The key in the dictionary is a regular expression search pattern, while the -value is a regular expression replacement pattern. It is therefore possible to -use back-references in the replacement string. - -*Warning*: Special characters in the acceptation of regular expressions _and_ -json both need to be escaped. For a key, this means, for example, that a -literal backslash must be escaped for the regular expression language `\\` -_and_ each of these backslashes must then be escaped for the json encoding: -`\\\\`. In the replacement string, a literal `$` must be escaped with an -additional `$` if it is followed by a number (due to the special meaning in -regular expression replacement strings), while a backslash must be escaped -(`\\`) due to the special meaning in json. - -*Advice*: To avoid unexpected results, always match terminating backslashes in -the search pattern and replacement string: if a search pattern ends with a -backslash, you should also put one in the replacement string, and if the search -pattern does not include a terminating backslash, then none should be included -in the replacement string. - -#### Examples explained - -```json -"IterationMaps": { - "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", - "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", - "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", -}, -"AreaMaps": { - "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", - "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", -} -``` - -- `"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",` - - In an iteration path, `OriginalProject\Path1` found at the beginning of the - path, when followed by `\Sprint 2022`, will be replaced by - `TargetProject\AnotherPath\NewTeam`. - - `OriginalProject\Path1\Sprint 2022\Sprint 01` will become - `TargetProject\AnotherPath\NewTeam\Sprint 2022\Sprint 01` but - `OriginalProject\Path1\Sprint 2020\Sprint 03` will _not_ be transformed by - this rule. - -- `"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",` - - In an iteration path, `OriginalProject\Path1` found at the beginning of the - path, when followed by `\Sprint 2020`, will be replaced by - `TargetProject\AnotherPath\Archives\\Sprints 2020`. - - `OriginalProject\Path1\Sprint 2020\Sprint 01` will become - `TargetProject\AnotherPath\Archives\Sprint 2020\Sprint 01` but - `OriginalProject\Path1\Sprint 2021\Sprint 03` will _not_ be transformed by - this rule. - -- `"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2",` - - In an iteration path, `OriginalProject\Path2` will be replaced by - `TargetProject\YetAnotherPath\Path2`. - -- `"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",` - - In an area path, `OriginalProject\` found at the beginning of the path, when - followed by either `DescopeThis` or `DescopeThat` will be replaced by `TargetProject\Archive\Descoped\`. - - `OriginalProject\DescopeThis\Area` will be transformed to - `TargetProject\Archive\Descoped\DescopeThis\Area`. - `OriginalProject\DescopeThat\Product` will be transformed to - `TargetProject\Archive\Descoped\DescopeThat\Product`. - -- `"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\",` - - In an area path, `OriginalProject\` found at the beginning of the path will be - replaced by `TargetProject\NewArea\` unless it is followed by `DescopeThis` or - `DescopeThat`. - - `OriginalProject\ValidArea\` would be replaced by - `TargetProject\NewArea\ValidArea\` but `OriginalProject\DescopeThis` would not - be modified by this rule. - -### More Complex Regex - -Before your migration starts it will validate that all of the Areas and Iterations from the **Source** work items revisions exist on the **Target**. Any that do not exist will be flagged in the logs and if and the migration will stop just after it outputs a list of the missing nodes. - -Our algorithm that converts the Source nodes to Target nodes processes the [mappings](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) at that time. This means that any valid mapped nodes will never be caught by the `This path is not anchored in the source project` message as they are already altered to be valid. - -> We recently updated the logging for this part of the system to more easily debug both your mappings and to see what they system is doing with the nodes and their current state. You can set `"LogLevel": "Debug"` to see the details. - -To add a mapping, you can follow [the documentation](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) with this being the simplest way: - -``` -"IterationMaps": { - "WorkItemMovedFromProjectName\\\\Iteration 1": "TargetProject\\Sprint 1", -}, -"AreaMaps": { - "WorkItemMovedFromProjectName\\\\Team 2": "TargetProject\\ProductA\\Team 2", -} -``` -Or you can use regular expressions to match the missing area or iteration paths: - -``` -"IterationMaps": { - "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", - "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", - "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", -}, -"AreaMaps": { - "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", - "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", -} -``` +## NodeBasePath Configuration -If you want to use the matches in the replacement you can use the following: +Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) -``` -"IterationMaps": { - "^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1\$2", -} -``` -If the olf iteration path was `\oldproject1\Custom Reporting\Sprint 13`, then this would result in a match for each Iteration node after the project node. You would then be able to reference any of the nodes using "$" and then the number of the match. - - -Regular expressions are much more difficult to build and debug so it is a good idea to use a [regular expression tester](https://regex101.com/) to check that you are matching the right things and to build them in ChatGTP. - -_NOTE: You need `\\` to escape a `\` the pattern, and `\\` to escape a `\` in JSON. Therefor on the left of the match you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the match_ +# Iteration Maps and Area Maps -![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) +Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) ## More Complex Team Migrations The above options allow you to bring over a sub-set of the WIs (using the `WIQLQueryBit`) and move their area or iteration path to a default location. However you may wish to do something more complex e.g. re-map the team structure. This can be done with addition of a `FieldMaps` block to configuration in addition to the `NodeBasePaths`. diff --git a/docs/Reference/v1/index.md b/docs/Reference/v1/index.md index 08ce424f4..b03375df3 100644 --- a/docs/Reference/v1/index.md +++ b/docs/Reference/v1/index.md @@ -21,5 +21,5 @@ manipulate the data during a migration. This config is for reference only. It has things configured that you will not need, and that may conflict with each other. {% highlight JSON %} -{% include sampleConfig/configuration-full.json %} +{% include sampleConfig/configration-demo-v15.0.json %} {% endhighlight %} diff --git a/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-introduction.md b/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-introduction.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md b/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md new file mode 100644 index 000000000..d445f930d --- /dev/null +++ b/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md @@ -0,0 +1,206 @@ +## NodeBasePath Configuration ## +The `NodeBasePaths` entry allows the filtering of the nodes to be replicated on the target projects. To try to explain the correct usage let us assume that we have a source team project `SourceProj` with the following node structures + +- AreaPath + - SourceProj + - SourceProj\Team 1 + - SourceProj\Team 2 + - SourceProj\Team 2\Sub-Area + - SourceProj\Team 3 +- IterationPath + - SourceProj + - SourceProj\Sprint 1 + - SourceProj\Sprint 2 + - SourceProj\Sprint 2\Sub-Iteration + - SourceProj\Sprint 3 + +Depending upon what node structures you wish to migrate you would need the following settings. Exclusions are also possible by prefixing a path with an exclamation mark `!`. Example are + +| | | +|-|-| +| Intention | Migrate all areas and iterations and all Work Items +| NodeBasePath | `[]` +| Comment | The same AreaPath and Iteration Paths are created on the target as on the source. Hence, all migrated WI remain in their existing area and iteration paths +|| +| Intention | Only migrate area path `Team 2` and it associated Work Items, but all iteration paths +| NodeBasePath | `["Team 2", "Sprint"]` +| Comment | Only the area path ending `Team 2` will be migrated.
The `WIQLQueryBit` should be edited to limit the WI migrated to this area path e.g. add `AND [System.AreaPath] UNDER 'SampleProject\\Team 2'` .
The migrated WI will have an area path of `TargetProj\Team 2` but retain their iteration paths matching the sprint name on the source +|| +| Intention | Only migrate iterations structure +| NodeBasePath | `["Sprint"]` +| Comment | Only the area path ending `Team 2` will be migrated
All the iteration paths will be migrated.
The migrated WI will have the default area path of `TargetProj` as their source area path was not migrated i.e. `TargetProj`
The migrated WI will have an iteration path match the sprint name on the source +|| +| Intention | Move all WI to the existing area and iteration paths on the targetProj +| NodeBasePath | `["DUMMY VALUE"]` +| Comment | As the `NodeBasePath` does not match any source area or iteration path no nodes are migrated.
Migrated WI will be assigned to any matching area or iteration paths. If no matching ones can be found they will default to the respective root values +|| +| Intention | Move the `Team 2` area, but not its `Sub-Area` +| NodeBasePath | `["Team 2", "!Team 2\\SubArea"]` +| Comment | The Work Items will have to be restricted to the right areas, e.g. with `AND [System.AreaPath] UNDER 'SampleProject\\Team 2' AND [System.AreaPath] NOT UNDER 'SampleProject\\Team 2\\Sub-Area'`, otherwise their migratin will fail + + +# Iteration Maps and Area Maps + +These two configuration elements apply after the `NodeBasePaths` selector, i.e. +only on Areas and Iterations that have been selected for migration. They allow +to change the area path, respectively the iteration path, of migrated work items. + +These remapping rules are applied both while creating path nodes in the target +project and when migrating work items. + +These remapping rules are applied with a higher priority than the +`PrefixProjectToNodes` option. This means that if no declared rule matches the +path and the `PrefixProjectToNodes` option is enabled, then the old behavior is +used. + +The syntax is a dictionary of regular expressions and the replacement text. + +*Warning*: These follow the +[.net regular expression language](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference). +The key in the dictionary is a regular expression search pattern, while the +value is a regular expression replacement pattern. It is therefore possible to +use back-references in the replacement string. + +*Warning*: Special characters in the acceptation of regular expressions _and_ +json both need to be escaped. For a key, this means, for example, that a +literal backslash must be escaped for the regular expression language `\\` +_and_ each of these backslashes must then be escaped for the json encoding: +`\\\\`. In the replacement string, a literal `$` must be escaped with an +additional `$` if it is followed by a number (due to the special meaning in +regular expression replacement strings), while a backslash must be escaped +(`\\`) due to the special meaning in json. + +*Advice*: To avoid unexpected results, always match terminating backslashes in +the search pattern and replacement string: if a search pattern ends with a +backslash, you should also put one in the replacement string, and if the search +pattern does not include a terminating backslash, then none should be included +in the replacement string. + +#### Examples explained + +```json +"IterationMaps": { + "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", + "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", + "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", +}, +"AreaMaps": { + "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", + "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", +} +``` + +- `"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",` + + In an iteration path, `OriginalProject\Path1` found at the beginning of the + path, when followed by `\Sprint 2022`, will be replaced by + `TargetProject\AnotherPath\NewTeam`. + + `OriginalProject\Path1\Sprint 2022\Sprint 01` will become + `TargetProject\AnotherPath\NewTeam\Sprint 2022\Sprint 01` but + `OriginalProject\Path1\Sprint 2020\Sprint 03` will _not_ be transformed by + this rule. + +- `"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",` + + In an iteration path, `OriginalProject\Path1` found at the beginning of the + path, when followed by `\Sprint 2020`, will be replaced by + `TargetProject\AnotherPath\Archives\\Sprints 2020`. + + `OriginalProject\Path1\Sprint 2020\Sprint 01` will become + `TargetProject\AnotherPath\Archives\Sprint 2020\Sprint 01` but + `OriginalProject\Path1\Sprint 2021\Sprint 03` will _not_ be transformed by + this rule. + +- `"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2",` + + In an iteration path, `OriginalProject\Path2` will be replaced by + `TargetProject\YetAnotherPath\Path2`. + +- `"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",` + + In an area path, `OriginalProject\` found at the beginning of the path, when + followed by either `DescopeThis` or `DescopeThat` will be replaced by `TargetProject\Archive\Descoped\`. + + `OriginalProject\DescopeThis\Area` will be transformed to + `TargetProject\Archive\Descoped\DescopeThis\Area`. + `OriginalProject\DescopeThat\Product` will be transformed to + `TargetProject\Archive\Descoped\DescopeThat\Product`. + +- `"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\",` + + In an area path, `OriginalProject\` found at the beginning of the path will be + replaced by `TargetProject\NewArea\` unless it is followed by `DescopeThis` or + `DescopeThat`. + + `OriginalProject\ValidArea\` would be replaced by + `TargetProject\NewArea\ValidArea\` but `OriginalProject\DescopeThis` would not + be modified by this rule. + +### More Complex Regex + +Before your migration starts it will validate that all of the Areas and Iterations from the **Source** work items revisions exist on the **Target**. Any that do not exist will be flagged in the logs and if and the migration will stop just after it outputs a list of the missing nodes. + +Our algorithm that converts the Source nodes to Target nodes processes the [mappings](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) at that time. This means that any valid mapped nodes will never be caught by the `This path is not anchored in the source project` message as they are already altered to be valid. + +> We recently updated the logging for this part of the system to more easily debug both your mappings and to see what they system is doing with the nodes and their current state. You can set `"LogLevel": "Debug"` to see the details. + +To add a mapping, you can follow [the documentation](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) with this being the simplest way: + +``` +"IterationMaps": { + "WorkItemMovedFromProjectName\\\\Iteration 1": "TargetProject\\Sprint 1", +}, +"AreaMaps": { + "WorkItemMovedFromProjectName\\\\Team 2": "TargetProject\\ProductA\\Team 2", +} +``` +Or you can use regular expressions to match the missing area or iteration paths: + +``` +"IterationMaps": { + "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", + "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", + "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", +}, +"AreaMaps": { + "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", + "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", +} +``` + +If you want to use the matches in the replacement you can use the following: + +``` +"IterationMaps": { + "^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1\$2", +} +``` +If the olf iteration path was `\oldproject1\Custom Reporting\Sprint 13`, then this would result in a match for each Iteration node after the project node. You would then be able to reference any of the nodes using "$" and then the number of the match. + + +Regular expressions are much more difficult to build and debug so it is a good idea to use a [regular expression tester](https://regex101.com/) to check that you are matching the right things and to build them in ChatGTP. + +_NOTE: You need `\\` to escape a `\` the pattern, and `\\` to escape a `\` in JSON. Therefor on the left of the match you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the match_ + +![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) + +### Example with AreaMaps and IterationMaps + +``` +"CommonEnrichersConfig": [ + { + "$type": "TfsNodeStructureOptions", + "PrefixProjectToNodes": false, + "NodeBasePaths": [], + "AreaMaps": { + "^Skypoint Cloud$" : "MigrationTest5" + }, + "IterationMaps": { + "^Skypoint Cloud\\\\Sprint 1$" : "MigrationTest5\\Sprint 1" + }, + "ShouldCreateMissingRevisionPaths": true, + "ReplicateAllExistingNodes": true + } +], +``` \ No newline at end of file diff --git a/docs/_data/reference.v1.processors.fixgitcommitlinks.yaml b/docs/_data/reference.v1.processors.fixgitcommitlinks.yaml index b57654edc..d16392079 100644 --- a/docs/_data/reference.v1.processors.fixgitcommitlinks.yaml +++ b/docs/_data/reference.v1.processors.fixgitcommitlinks.yaml @@ -8,8 +8,7 @@ configurationSamples: "$type": "FixGitCommitLinksConfig", "Enabled": false, "TargetRepository": null, - "QueryBit": null, - "OrderBit": null + "Query": null } sampleFor: MigrationTools._EngineV1.Configuration.Processing.FixGitCommitLinksConfig description: missng XML code comments @@ -21,11 +20,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: OrderBit - type: String - description: missng XML code comments - defaultValue: missng XML code comments -- parameterName: QueryBit +- parameterName: Query type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/_data/reference.v1.processors.testplansandsuitesmigrationcontext.yaml b/docs/_data/reference.v1.processors.testplansandsuitesmigrationcontext.yaml index 57c0173b8..df4cc82b9 100644 --- a/docs/_data/reference.v1.processors.testplansandsuitesmigrationcontext.yaml +++ b/docs/_data/reference.v1.processors.testplansandsuitesmigrationcontext.yaml @@ -7,15 +7,10 @@ configurationSamples: { "$type": "TestPlansAndSuitesMigrationConfig", "Enabled": false, - "PrefixProjectToNodes": false, "OnlyElementsWithTag": null, - "TestPlanQueryBit": null, + "TestPlanQuery": null, "RemoveAllLinks": false, "MigrationDelay": 0, - "UseCommonNodeStructureEnricherConfig": false, - "NodeBasePaths": null, - "AreaMaps": null, - "IterationMaps": null, "RemoveInvalidTestSuiteLinks": false, "FilterCompleted": false } @@ -25,10 +20,6 @@ className: TestPlansAndSuitesMigrationContext typeName: Processors architecture: v1 options: -- parameterName: AreaMaps - type: Dictionary - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: null - parameterName: Enabled type: Boolean description: missng XML code comments @@ -37,26 +28,14 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: IterationMaps - type: Dictionary - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: null - parameterName: MigrationDelay type: Int32 description: ??Not sure what this does. Check code. defaultValue: 0 -- parameterName: NodeBasePaths - type: String[] - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: '[]' - parameterName: OnlyElementsWithTag type: String description: The tag name that is present on all elements that must be migrated. If this option isn't present this processor will migrate all. defaultValue: '`String.Empty`' -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix the nodes with the new project name. - defaultValue: false - parameterName: RemoveAllLinks type: Boolean description: ??Not sure what this does. Check code. @@ -65,14 +44,10 @@ options: type: Boolean description: Remove Invalid Links, see https://github.com/nkdAgility/azure-devops-migration-tools/issues/178 defaultValue: missng XML code comments -- parameterName: TestPlanQueryBit +- parameterName: TestPlanQuery type: String description: Filtering conditions to decide whether to migrate a test plan or not. When provided, this partial query is added after `Select * From TestPlan Where` when selecting test plans. Among filtering options, `AreaPath`, `PlanName` and `PlanState` are known to work. There is unfortunately no documentation regarding the available fields. defaultValue: '`String.Empty`' -- parameterName: UseCommonNodeStructureEnricherConfig - type: Boolean - description: Indicates whether the configuration for node structure transformation should be taken from the common enricher configs. Otherwise the configuration elements below are used - defaultValue: false status: Beta processingTarget: Suites & Plans classFile: /src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs diff --git a/docs/_data/reference.v1.processors.workitemdelete.yaml b/docs/_data/reference.v1.processors.workitemdelete.yaml index 0260966c2..80c848e8d 100644 --- a/docs/_data/reference.v1.processors.workitemdelete.yaml +++ b/docs/_data/reference.v1.processors.workitemdelete.yaml @@ -7,8 +7,7 @@ configurationSamples: { "$type": "WorkItemDeleteConfig", "Enabled": false, - "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - "WIQLOrderBit": "[System.ChangedDate] desc", + "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", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, @@ -32,11 +31,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: WIQLOrderBit - type: String - description: missng XML code comments - defaultValue: missng XML code comments -- parameterName: WIQLQueryBit +- parameterName: WIQLQuery type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml b/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml index b75f5f949..aae0c9593 100644 --- a/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml +++ b/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml @@ -7,8 +7,6 @@ configurationSamples: { "$type": "WorkItemMigrationConfig", "Enabled": false, - "ReplayRevisions": true, - "PrefixProjectToNodes": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request')", @@ -26,19 +24,9 @@ configurationSamples: "LinkMigrationSaveEachAsAdded": false, "GenerateMigrationComment": true, "WorkItemIDs": null, - "MaxRevisions": 0, - "UseCommonNodeStructureEnricherConfig": false, - "NodeBasePaths": null, - "AreaMaps": { - "$type": "Dictionary`2" - }, - "IterationMaps": { - "$type": "Dictionary`2" - }, "MaxGracefulFailures": 0, "SkipRevisionWithInvalidIterationPath": false, - "SkipRevisionWithInvalidAreaPath": false, - "ShouldCreateMissingRevisionPaths": true + "SkipRevisionWithInvalidAreaPath": false } sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemMigrationConfig description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure. @@ -46,10 +34,6 @@ className: WorkItemMigrationContext typeName: Processors architecture: v1 options: -- parameterName: AreaMaps - type: Dictionary - description: Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - defaultValue: '{}' - parameterName: AttachmentMaxSize type: Int32 description: '`AttachmentMigration` is set to true then you need to specify a max file size for upload in bites. For Azure DevOps Services the default is 480,000,000 bites (60mb), for TFS its 32,000,000 bites (4mb).' @@ -82,10 +66,6 @@ options: type: Boolean description: If enabled, adds a comment recording the migration defaultValue: false -- parameterName: IterationMaps - type: Dictionary - description: Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - defaultValue: '{}' - parameterName: LinkMigration type: Boolean description: If enabled this will migrate the Links for the work item at the same time as the whole work item. @@ -98,30 +78,10 @@ options: 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: MaxRevisions - type: Int32 - description: Sets the maximum number of revisions that will be migrated. "First + Last N = Max". If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. - defaultValue: 0 -- parameterName: NodeBasePaths - type: String[] - description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) - defaultValue: '["/"]' - parameterName: PauseAfterEachWorkItem type: Boolean description: Pause after each work item is migrated defaultValue: false -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false -- parameterName: ReplayRevisions - type: Boolean - description: You can choose to migrate the tip only (a single write) or all of the revisions (many writes). If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. - defaultValue: true -- parameterName: ShouldCreateMissingRevisionPaths - type: Boolean - description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. - 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. @@ -142,10 +102,6 @@ options: 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: UseCommonNodeStructureEnricherConfig - type: Boolean - description: '' - defaultValue: '?' - parameterName: WIQLOrderBit type: String description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. diff --git a/docs/_data/reference.v1.processors.workitempostprocessingcontext.yaml b/docs/_data/reference.v1.processors.workitempostprocessingcontext.yaml index 730b61960..bf8a3b049 100644 --- a/docs/_data/reference.v1.processors.workitempostprocessingcontext.yaml +++ b/docs/_data/reference.v1.processors.workitempostprocessingcontext.yaml @@ -8,8 +8,7 @@ configurationSamples: "$type": "WorkItemPostProcessingConfig", "Enabled": false, "WorkItemIDs": null, - "WIQLQueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' ", - "WIQLOrderBit": null, + "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", "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, "WorkItemCreateRetryLimit": 0 @@ -32,11 +31,7 @@ options: type: Boolean description: Pause after each work item is migrated defaultValue: false -- parameterName: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') diff --git a/docs/_data/reference.v1.processors.workitemupdate.yaml b/docs/_data/reference.v1.processors.workitemupdate.yaml index efedd71dc..8b64b0fd6 100644 --- a/docs/_data/reference.v1.processors.workitemupdate.yaml +++ b/docs/_data/reference.v1.processors.workitemupdate.yaml @@ -8,8 +8,7 @@ configurationSamples: "$type": "WorkItemUpdateConfig", "Enabled": false, "WhatIf": false, - "WIQLQueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')", - "WIQLOrderBit": null, + "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", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, @@ -37,11 +36,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') diff --git a/docs/_data/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.yaml b/docs/_data/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.yaml index 5fc176cc0..e0eafe56e 100644 --- a/docs/_data/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.yaml +++ b/docs/_data/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.yaml @@ -16,11 +16,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.yaml b/docs/_data/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.yaml index c09cd8145..aeff62c9a 100644 --- a/docs/_data/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.yaml +++ b/docs/_data/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.yaml @@ -21,7 +21,7 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: Query type: QueryOptions @@ -29,7 +29,7 @@ options: defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.pauseaftereachitem.yaml b/docs/_data/reference.v2.processorenrichers.pauseaftereachitem.yaml index 970f7d3e4..4528f9a16 100644 --- a/docs/_data/reference.v2.processorenrichers.pauseaftereachitem.yaml +++ b/docs/_data/reference.v2.processorenrichers.pauseaftereachitem.yaml @@ -16,11 +16,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.yaml b/docs/_data/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.yaml index a08119229..d11ded4d3 100644 --- a/docs/_data/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.yaml +++ b/docs/_data/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.yaml @@ -16,11 +16,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml b/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml index c1aae7cb7..a8c5ad26b 100644 --- a/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml +++ b/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml @@ -19,34 +19,34 @@ configurationSamples: "ReplicateAllExistingNodes": false } sampleFor: MigrationTools.Enrichers.TfsNodeStructureOptions -description: missng XML code comments +description: The TfsNodeStructureEnricher is used to create missing nodes in the target project. To configure it add a `TfsNodeStructureOptions` section to `CommonEnrichersConfig` in the config file. Otherwise defaults will be applied. className: TfsNodeStructure typeName: ProcessorEnrichers architecture: v2 options: - parameterName: AreaMaps type: Dictionary - description: missng XML code comments - defaultValue: missng XML code comments + description: Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + defaultValue: '{}' - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: IterationMaps type: Dictionary - description: missng XML code comments - defaultValue: missng XML code comments + description: Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + defaultValue: '{}' - parameterName: NodeBasePaths type: String[] - description: missng XML code comments - defaultValue: missng XML code comments + description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) + defaultValue: '["/"]' - parameterName: PrefixProjectToNodes type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments + description: Prefix the nodes with the new project name. + defaultValue: false - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: ReplicateAllExistingNodes type: Boolean @@ -54,7 +54,7 @@ options: defaultValue: missng XML code comments - parameterName: ShouldCreateMissingRevisionPaths type: Boolean - description: missng XML code comments + description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.tfsrevisionmanager.yaml b/docs/_data/reference.v2.processorenrichers.tfsrevisionmanager.yaml index f4c5262a1..2a9e33561 100644 --- a/docs/_data/reference.v2.processorenrichers.tfsrevisionmanager.yaml +++ b/docs/_data/reference.v2.processorenrichers.tfsrevisionmanager.yaml @@ -7,31 +7,31 @@ configurationSamples: { "$type": "TfsRevisionManagerOptions", "Enabled": true, - "ReplayRevisions": false, + "ReplayRevisions": true, "MaxRevisions": 0 } sampleFor: MigrationTools.Enrichers.TfsRevisionManagerOptions -description: missng XML code comments +description: The TfsRevisionManager manipulates the revisions of a work item to reduce the number of revisions that are migrated. className: TfsRevisionManager typeName: ProcessorEnrichers architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: MaxRevisions type: Int32 - description: missng XML code comments - defaultValue: missng XML code comments + description: Sets the maximum number of revisions that will be migrated. "First + Last N = Max". If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. + defaultValue: 0 - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: ReplayRevisions type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments + description: You can choose to migrate the tip only (a single write) or all of the revisions (many writes). If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. + defaultValue: true status: missng XML code comments processingTarget: missng XML code comments classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs diff --git a/docs/_data/reference.v2.processorenrichers.tfsvalidaterequiredfield.yaml b/docs/_data/reference.v2.processorenrichers.tfsvalidaterequiredfield.yaml index 045cd6eef..0506ecf29 100644 --- a/docs/_data/reference.v2.processorenrichers.tfsvalidaterequiredfield.yaml +++ b/docs/_data/reference.v2.processorenrichers.tfsvalidaterequiredfield.yaml @@ -16,11 +16,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/_data/reference.v2.processorenrichers.tfsworkitemlinkenricher.yaml b/docs/_data/reference.v2.processorenrichers.tfsworkitemlinkenricher.yaml index 8701c2f99..64f646e26 100644 --- a/docs/_data/reference.v2.processorenrichers.tfsworkitemlinkenricher.yaml +++ b/docs/_data/reference.v2.processorenrichers.tfsworkitemlinkenricher.yaml @@ -1,12 +1,38 @@ -optionsClassName: -optionsClassFullName: -configurationSamples: [] +optionsClassName: TfsWorkItemLinkEnricherOptions +optionsClassFullName: MigrationTools.Enrichers.TfsWorkItemLinkEnricherOptions +configurationSamples: +- name: default + description: + code: >- + { + "$type": "TfsWorkItemLinkEnricherOptions", + "Enabled": true, + "FilterIfLinkCountMatches": true, + "SaveAfterEachLinkIsAdded": false + } + sampleFor: MigrationTools.Enrichers.TfsWorkItemLinkEnricherOptions description: missng XML code comments className: TfsWorkItemLinkEnricher typeName: ProcessorEnrichers architecture: v2 -options: [] +options: +- parameterName: Enabled + type: Boolean + description: For internal use + defaultValue: missng XML code comments +- parameterName: FilterIfLinkCountMatches + type: Boolean + description: Skip validating links if the number of links in the source and the target matches! + defaultValue: missng XML code comments +- parameterName: RefName + type: String + description: For internal use + defaultValue: missng XML code comments +- parameterName: SaveAfterEachLinkIsAdded + type: Boolean + description: Save the work item after each link is added. This will slow the migration as it will cause many saves to the TFS database. + defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments -classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/Enrichers/TfsWorkItemLinkEnricher.cs -optionsClassFile: +classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricher.cs +optionsClassFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs diff --git a/docs/_includes/sampleConfig/configration-demo-v15.0.json b/docs/_includes/sampleConfig/configration-demo-v15.0.json new file mode 100644 index 000000000..108b59d6c --- /dev/null +++ b/docs/_includes/sampleConfig/configration-demo-v15.0.json @@ -0,0 +1,102 @@ +{ + "ChangeSetMappingFile": null, + "Source": { + "$type": "TfsTeamProjectConfig", + "Collection": "https://dev.azure.com/nkdagility-preview/", + "Project": "migrationSource1", + "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "AllowCrossProjectLinking": false, + "AuthenticationMode": "Prompt", + "PersonalAccessToken": "", + "PersonalAccessTokenVariableName": "", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + } + }, + "Target": { + "$type": "TfsTeamProjectConfig", + "Collection": "https://dev.azure.com/nkdagility-preview/", + "Project": "migrationTest5", + "ReflectedWorkItemIDFieldName": "nkdScrum.ReflectedWorkItemId", + "AllowCrossProjectLinking": false, + "AuthenticationMode": "Prompt", + "PersonalAccessToken": "njp3kcec4nbev63fmbepvdpn35drawmonk5qf5yqsw77dgfwnjda", + "PersonalAccessTokenVariableName": "", + "LanguageMaps": { + "AreaPath": "Area", + "IterationPath": "Iteration" + } + }, + "FieldMaps": [], + "GitRepoMapping": null, + "LogLevel": "Debug", + "CommonEnrichersConfig": [ + { + "$type": "TfsNodeStructureOptions", + "PrefixProjectToNodes": false, + "NodeBasePaths": [], + "AreaMaps": { + "^Skypoint Cloud$": "MigrationTest5" + }, + "IterationMaps": { + "^Skypoint Cloud\\\\Sprint 1$": "MigrationTest5\\Sprint 1" + }, + "ShouldCreateMissingRevisionPaths": true, + "ReplicateAllExistingNodes": true + }, + { + "$type": "TfsWorkItemLinkEnricherOptions", + "Enabled": true, + "FilterIfLinkCountMatches": true, + "SaveAfterEachLinkIsAdded": false + }, + { + "$type": "TfsRevisionManagerOptions", + "Enabled": true, + "ReplayRevisions": true, + "MaxRevisions": 0 + } + ], + "Processors": [ + { + "$type": "WorkItemMigrationConfig", + "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", + "LinkMigration": true, + "AttachmentMigration": true, + "AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\", + "FixHtmlAttachmentLinks": false, + "SkipToFinalRevisedWorkItemType": false, + "WorkItemCreateRetryLimit": 5, + "FilterWorkItemsThatAlreadyExistInTarget": false, + "PauseAfterEachWorkItem": false, + "AttachmentMaxSize": 480000000, + "AttachRevisionHistory": false, + "GenerateMigrationComment": true, + "WorkItemIDs": null, + "MaxGracefulFailures": 0, + "SkipRevisionWithInvalidIterationPath": false, + "SkipRevisionWithInvalidAreaPath": false + } + ], + "Version": "15.0", + "workaroundForQuerySOAPBugEnabled": false, + "WorkItemTypeDefinition": { + "sourceWorkItemTypeName": "targetWorkItemTypeName" + }, + "Endpoints": { + "InMemoryWorkItemEndpoints": [ + { + "Name": "Source", + "EndpointEnrichers": null + }, + { + "Name": "Target", + "EndpointEnrichers": null + } + ] + } +} \ No newline at end of file diff --git a/docs/collections/_reference/reference.v1.processors.fixgitcommitlinks.md b/docs/collections/_reference/reference.v1.processors.fixgitcommitlinks.md index 12601217a..3d41327ad 100644 --- a/docs/collections/_reference/reference.v1.processors.fixgitcommitlinks.md +++ b/docs/collections/_reference/reference.v1.processors.fixgitcommitlinks.md @@ -9,8 +9,7 @@ configurationSamples: "$type": "FixGitCommitLinksConfig", "Enabled": false, "TargetRepository": null, - "QueryBit": null, - "OrderBit": null + "Query": null } sampleFor: MigrationTools._EngineV1.Configuration.Processing.FixGitCommitLinksConfig description: missng XML code comments @@ -22,11 +21,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: OrderBit - type: String - description: missng XML code comments - defaultValue: missng XML code comments -- parameterName: QueryBit +- parameterName: Query type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/collections/_reference/reference.v1.processors.testplansandsuitesmigrationcontext.md b/docs/collections/_reference/reference.v1.processors.testplansandsuitesmigrationcontext.md index 82aafd8ce..abbe01f15 100644 --- a/docs/collections/_reference/reference.v1.processors.testplansandsuitesmigrationcontext.md +++ b/docs/collections/_reference/reference.v1.processors.testplansandsuitesmigrationcontext.md @@ -8,15 +8,10 @@ configurationSamples: { "$type": "TestPlansAndSuitesMigrationConfig", "Enabled": false, - "PrefixProjectToNodes": false, "OnlyElementsWithTag": null, - "TestPlanQueryBit": null, + "TestPlanQuery": null, "RemoveAllLinks": false, "MigrationDelay": 0, - "UseCommonNodeStructureEnricherConfig": false, - "NodeBasePaths": null, - "AreaMaps": null, - "IterationMaps": null, "RemoveInvalidTestSuiteLinks": false, "FilterCompleted": false } @@ -26,10 +21,6 @@ className: TestPlansAndSuitesMigrationContext typeName: Processors architecture: v1 options: -- parameterName: AreaMaps - type: Dictionary - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: null - parameterName: Enabled type: Boolean description: missng XML code comments @@ -38,26 +29,14 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: IterationMaps - type: Dictionary - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: null - parameterName: MigrationDelay type: Int32 description: ??Not sure what this does. Check code. defaultValue: 0 -- parameterName: NodeBasePaths - type: String[] - description: See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - defaultValue: '[]' - parameterName: OnlyElementsWithTag type: String description: The tag name that is present on all elements that must be migrated. If this option isn't present this processor will migrate all. defaultValue: '`String.Empty`' -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix the nodes with the new project name. - defaultValue: false - parameterName: RemoveAllLinks type: Boolean description: ??Not sure what this does. Check code. @@ -66,14 +45,10 @@ options: type: Boolean description: Remove Invalid Links, see https://github.com/nkdAgility/azure-devops-migration-tools/issues/178 defaultValue: missng XML code comments -- parameterName: TestPlanQueryBit +- parameterName: TestPlanQuery type: String description: Filtering conditions to decide whether to migrate a test plan or not. When provided, this partial query is added after `Select * From TestPlan Where` when selecting test plans. Among filtering options, `AreaPath`, `PlanName` and `PlanState` are known to work. There is unfortunately no documentation regarding the available fields. defaultValue: '`String.Empty`' -- parameterName: UseCommonNodeStructureEnricherConfig - type: Boolean - description: Indicates whether the configuration for node structure transformation should be taken from the common enricher configs. Otherwise the configuration elements below are used - defaultValue: false status: Beta processingTarget: Suites & Plans classFile: /src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs diff --git a/docs/collections/_reference/reference.v1.processors.workitemdelete.md b/docs/collections/_reference/reference.v1.processors.workitemdelete.md index a02667916..94ac01af9 100644 --- a/docs/collections/_reference/reference.v1.processors.workitemdelete.md +++ b/docs/collections/_reference/reference.v1.processors.workitemdelete.md @@ -8,8 +8,7 @@ configurationSamples: { "$type": "WorkItemDeleteConfig", "Enabled": false, - "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - "WIQLOrderBit": "[System.ChangedDate] desc", + "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", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, @@ -33,11 +32,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: WIQLOrderBit - type: String - description: missng XML code comments - defaultValue: missng XML code comments -- parameterName: WIQLQueryBit +- parameterName: WIQLQuery type: String description: missng XML code comments defaultValue: missng XML code comments diff --git a/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md b/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md index 6c68a255f..2bdf53394 100644 --- a/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md +++ b/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md @@ -8,8 +8,6 @@ configurationSamples: { "$type": "WorkItemMigrationConfig", "Enabled": false, - "ReplayRevisions": true, - "PrefixProjectToNodes": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request')", @@ -27,19 +25,9 @@ configurationSamples: "LinkMigrationSaveEachAsAdded": false, "GenerateMigrationComment": true, "WorkItemIDs": null, - "MaxRevisions": 0, - "UseCommonNodeStructureEnricherConfig": false, - "NodeBasePaths": null, - "AreaMaps": { - "$type": "Dictionary`2" - }, - "IterationMaps": { - "$type": "Dictionary`2" - }, "MaxGracefulFailures": 0, "SkipRevisionWithInvalidIterationPath": false, - "SkipRevisionWithInvalidAreaPath": false, - "ShouldCreateMissingRevisionPaths": true + "SkipRevisionWithInvalidAreaPath": false } sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemMigrationConfig description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure. @@ -47,10 +35,6 @@ className: WorkItemMigrationContext typeName: Processors architecture: v1 options: -- parameterName: AreaMaps - type: Dictionary - description: Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - defaultValue: '{}' - parameterName: AttachmentMaxSize type: Int32 description: '`AttachmentMigration` is set to true then you need to specify a max file size for upload in bites. For Azure DevOps Services the default is 480,000,000 bites (60mb), for TFS its 32,000,000 bites (4mb).' @@ -83,10 +67,6 @@ options: type: Boolean description: If enabled, adds a comment recording the migration defaultValue: false -- parameterName: IterationMaps - type: Dictionary - description: Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - defaultValue: '{}' - parameterName: LinkMigration type: Boolean description: If enabled this will migrate the Links for the work item at the same time as the whole work item. @@ -99,30 +79,10 @@ options: 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: MaxRevisions - type: Int32 - description: Sets the maximum number of revisions that will be migrated. "First + Last N = Max". If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. - defaultValue: 0 -- parameterName: NodeBasePaths - type: String[] - description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) - defaultValue: '["/"]' - parameterName: PauseAfterEachWorkItem type: Boolean description: Pause after each work item is migrated defaultValue: false -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false -- parameterName: ReplayRevisions - type: Boolean - description: You can choose to migrate the tip only (a single write) or all of the revisions (many writes). If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. - defaultValue: true -- parameterName: ShouldCreateMissingRevisionPaths - type: Boolean - description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. - 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. @@ -143,10 +103,6 @@ options: 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: UseCommonNodeStructureEnricherConfig - type: Boolean - description: '' - defaultValue: '?' - parameterName: WIQLOrderBit type: String description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. @@ -180,7 +136,8 @@ topics: - topic: notes path: /docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md exists: true - markdown: >- + markdown: >2- + ## WIQL Query Bits @@ -244,270 +201,16 @@ topics: ``` - ## NodeBasePath Configuration ## - - The `NodeBasePaths` entry allows the filtering of the nodes to be replicated on the target projects. To try to explain the correct usage let us assume that we have a source team project `SourceProj` with the following node structures - - - - AreaPath - - SourceProj - - SourceProj\Team 1 - - SourceProj\Team 2 - - SourceProj\Team 2\Sub-Area - - SourceProj\Team 3 - - IterationPath - - SourceProj - - SourceProj\Sprint 1 - - SourceProj\Sprint 2 - - SourceProj\Sprint 2\Sub-Iteration - - SourceProj\Sprint 3 - - Depending upon what node structures you wish to migrate you would need the following settings. Exclusions are also possible by prefixing a path with an exclamation mark `!`. Example are - - - | | | - - |-|-| - - | Intention | Migrate all areas and iterations and all Work Items - - | NodeBasePath | `[]` - - | Comment | The same AreaPath and Iteration Paths are created on the target as on the source. Hence, all migrated WI remain in their existing area and iteration paths - - || - - | Intention | Only migrate area path `Team 2` and it associated Work Items, but all iteration paths - - | NodeBasePath | `["Team 2", "Sprint"]` - - | Comment | Only the area path ending `Team 2` will be migrated.
The `WIQLQueryBit` should be edited to limit the WI migrated to this area path e.g. add `AND [System.AreaPath] UNDER 'SampleProject\\Team 2'` .
The migrated WI will have an area path of `TargetProj\Team 2` but retain their iteration paths matching the sprint name on the source - - || - - | Intention | Only migrate iterations structure - - | NodeBasePath | `["Sprint"]` - - | Comment | Only the area path ending `Team 2` will be migrated
All the iteration paths will be migrated.
The migrated WI will have the default area path of `TargetProj` as their source area path was not migrated i.e. `TargetProj`
The migrated WI will have an iteration path match the sprint name on the source - - || - - | Intention | Move all WI to the existing area and iteration paths on the targetProj - - | NodeBasePath | `["DUMMY VALUE"]` - - | Comment | As the `NodeBasePath` does not match any source area or iteration path no nodes are migrated.
Migrated WI will be assigned to any matching area or iteration paths. If no matching ones can be found they will default to the respective root values - - || + ## NodeBasePath Configuration - | Intention | Move the `Team 2` area, but not its `Sub-Area` - | NodeBasePath | `["Team 2", "!Team 2\\SubArea"]` - - | Comment | The Work Items will have to be restricted to the right areas, e.g. with `AND [System.AreaPath] UNDER 'SampleProject\\Team 2' AND [System.AreaPath] NOT UNDER 'SampleProject\\Team 2\\Sub-Area'`, otherwise their migratin will fail + Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) # Iteration Maps and Area Maps - These two configuration elements apply after the `NodeBasePaths` selector, i.e. - - only on Areas and Iterations that have been selected for migration. They allow - - to change the area path, respectively the iteration path, of migrated work items. - - - These remapping rules are applied both while creating path nodes in the target - - project and when migrating work items. - - - These remapping rules are applied with a higher priority than the - - `PrefixProjectToNodes` option. This means that if no declared rule matches the - - path and the `PrefixProjectToNodes` option is enabled, then the old behavior is - - used. - - - The syntax is a dictionary of regular expressions and the replacement text. - - - *Warning*: These follow the - - [.net regular expression language](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference). - - The key in the dictionary is a regular expression search pattern, while the - - value is a regular expression replacement pattern. It is therefore possible to - - use back-references in the replacement string. - - - *Warning*: Special characters in the acceptation of regular expressions _and_ - - json both need to be escaped. For a key, this means, for example, that a - - literal backslash must be escaped for the regular expression language `\\` - - _and_ each of these backslashes must then be escaped for the json encoding: - - `\\\\`. In the replacement string, a literal `$` must be escaped with an - - additional `$` if it is followed by a number (due to the special meaning in - - regular expression replacement strings), while a backslash must be escaped - - (`\\`) due to the special meaning in json. - - - *Advice*: To avoid unexpected results, always match terminating backslashes in - - the search pattern and replacement string: if a search pattern ends with a - - backslash, you should also put one in the replacement string, and if the search - - pattern does not include a terminating backslash, then none should be included - - in the replacement string. - - - #### Examples explained - - - ```json - - "IterationMaps": { - "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", - "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", - "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", - }, - - "AreaMaps": { - "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", - "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", - } - - ``` - - - - `"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",` - - In an iteration path, `OriginalProject\Path1` found at the beginning of the - path, when followed by `\Sprint 2022`, will be replaced by - `TargetProject\AnotherPath\NewTeam`. - - `OriginalProject\Path1\Sprint 2022\Sprint 01` will become - `TargetProject\AnotherPath\NewTeam\Sprint 2022\Sprint 01` but - `OriginalProject\Path1\Sprint 2020\Sprint 03` will _not_ be transformed by - this rule. - - - `"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",` - - In an iteration path, `OriginalProject\Path1` found at the beginning of the - path, when followed by `\Sprint 2020`, will be replaced by - `TargetProject\AnotherPath\Archives\\Sprints 2020`. - - `OriginalProject\Path1\Sprint 2020\Sprint 01` will become - `TargetProject\AnotherPath\Archives\Sprint 2020\Sprint 01` but - `OriginalProject\Path1\Sprint 2021\Sprint 03` will _not_ be transformed by - this rule. - - - `"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2",` - - In an iteration path, `OriginalProject\Path2` will be replaced by - `TargetProject\YetAnotherPath\Path2`. - - - `"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",` - - In an area path, `OriginalProject\` found at the beginning of the path, when - followed by either `DescopeThis` or `DescopeThat` will be replaced by `TargetProject\Archive\Descoped\`. - - `OriginalProject\DescopeThis\Area` will be transformed to - `TargetProject\Archive\Descoped\DescopeThis\Area`. - `OriginalProject\DescopeThat\Product` will be transformed to - `TargetProject\Archive\Descoped\DescopeThat\Product`. - - - `"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\",` - - In an area path, `OriginalProject\` found at the beginning of the path will be - replaced by `TargetProject\NewArea\` unless it is followed by `DescopeThis` or - `DescopeThat`. - - `OriginalProject\ValidArea\` would be replaced by - `TargetProject\NewArea\ValidArea\` but `OriginalProject\DescopeThis` would not - be modified by this rule. - - ### More Complex Regex - - - Before your migration starts it will validate that all of the Areas and Iterations from the **Source** work items revisions exist on the **Target**. Any that do not exist will be flagged in the logs and if and the migration will stop just after it outputs a list of the missing nodes. - - - Our algorithm that converts the Source nodes to Target nodes processes the [mappings](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) at that time. This means that any valid mapped nodes will never be caught by the `This path is not anchored in the source project` message as they are already altered to be valid. - - - > We recently updated the logging for this part of the system to more easily debug both your mappings and to see what they system is doing with the nodes and their current state. You can set `"LogLevel": "Debug"` to see the details. - - - To add a mapping, you can follow [the documentation](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) with this being the simplest way: - - - ``` - - "IterationMaps": { - "WorkItemMovedFromProjectName\\\\Iteration 1": "TargetProject\\Sprint 1", - }, - - "AreaMaps": { - "WorkItemMovedFromProjectName\\\\Team 2": "TargetProject\\ProductA\\Team 2", - } - - ``` - - Or you can use regular expressions to match the missing area or iteration paths: - - - ``` - - "IterationMaps": { - "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", - "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", - "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", - }, - - "AreaMaps": { - "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", - "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", - } - - ``` - - - If you want to use the matches in the replacement you can use the following: - - - ``` - - "IterationMaps": { - "^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1\$2", - } - - ``` - - If the olf iteration path was `\oldproject1\Custom Reporting\Sprint 13`, then this would result in a match for each Iteration node after the project node. You would then be able to reference any of the nodes using "$" and then the number of the match. - - - - Regular expressions are much more difficult to build and debug so it is a good idea to use a [regular expression tester](https://regex101.com/) to check that you are matching the right things and to build them in ChatGTP. - - - _NOTE: You need `\\` to escape a `\` the pattern, and `\\` to escape a `\` in JSON. Therefor on the left of the match you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the match_ - - - ![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) + Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) ## More Complex Team Migrations diff --git a/docs/collections/_reference/reference.v1.processors.workitempostprocessingcontext.md b/docs/collections/_reference/reference.v1.processors.workitempostprocessingcontext.md index d7669fb8e..5c59a79d7 100644 --- a/docs/collections/_reference/reference.v1.processors.workitempostprocessingcontext.md +++ b/docs/collections/_reference/reference.v1.processors.workitempostprocessingcontext.md @@ -9,8 +9,7 @@ configurationSamples: "$type": "WorkItemPostProcessingConfig", "Enabled": false, "WorkItemIDs": null, - "WIQLQueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' ", - "WIQLOrderBit": null, + "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", "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, "WorkItemCreateRetryLimit": 0 @@ -33,11 +32,7 @@ options: type: Boolean description: Pause after each work item is migrated defaultValue: false -- parameterName: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') diff --git a/docs/collections/_reference/reference.v1.processors.workitemupdate.md b/docs/collections/_reference/reference.v1.processors.workitemupdate.md index 2ce0c561a..6dda7c76b 100644 --- a/docs/collections/_reference/reference.v1.processors.workitemupdate.md +++ b/docs/collections/_reference/reference.v1.processors.workitemupdate.md @@ -9,8 +9,7 @@ configurationSamples: "$type": "WorkItemUpdateConfig", "Enabled": false, "WhatIf": false, - "WIQLQueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')", - "WIQLOrderBit": null, + "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", "WorkItemIDs": null, "FilterWorkItemsThatAlreadyExistInTarget": false, "PauseAfterEachWorkItem": false, @@ -38,11 +37,7 @@ options: type: Boolean description: missng XML code comments defaultValue: missng XML code comments -- parameterName: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') diff --git a/docs/collections/_reference/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.md b/docs/collections/_reference/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.md index 67b869b66..37f854aca 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.appendmigrationtoolsignaturefooter.md @@ -17,11 +17,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/collections/_reference/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.md b/docs/collections/_reference/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.md index af2b08249..35539777d 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.filterworkitemsthatalreadyexistintarget.md @@ -22,7 +22,7 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: Query type: QueryOptions @@ -30,7 +30,7 @@ options: defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/collections/_reference/reference.v2.processorenrichers.pauseaftereachitem.md b/docs/collections/_reference/reference.v2.processorenrichers.pauseaftereachitem.md index 6fe951a76..8c24ffc86 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.pauseaftereachitem.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.pauseaftereachitem.md @@ -17,11 +17,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/collections/_reference/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.md b/docs/collections/_reference/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.md index 106e617d9..7de1ea639 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.skiptofinalrevisedworkitemtype.md @@ -17,11 +17,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md b/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md index 4710440ec..8b5175028 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md @@ -20,34 +20,34 @@ configurationSamples: "ReplicateAllExistingNodes": false } sampleFor: MigrationTools.Enrichers.TfsNodeStructureOptions -description: missng XML code comments +description: The TfsNodeStructureEnricher is used to create missing nodes in the target project. To configure it add a `TfsNodeStructureOptions` section to `CommonEnrichersConfig` in the config file. Otherwise defaults will be applied. className: TfsNodeStructure typeName: ProcessorEnrichers architecture: v2 options: - parameterName: AreaMaps type: Dictionary - description: missng XML code comments - defaultValue: missng XML code comments + description: Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + defaultValue: '{}' - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: IterationMaps type: Dictionary - description: missng XML code comments - defaultValue: missng XML code comments + description: Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + defaultValue: '{}' - parameterName: NodeBasePaths type: String[] - description: missng XML code comments - defaultValue: missng XML code comments + description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) + defaultValue: '["/"]' - parameterName: PrefixProjectToNodes type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments + description: Prefix the nodes with the new project name. + defaultValue: false - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: ReplicateAllExistingNodes type: Boolean @@ -55,7 +55,7 @@ options: defaultValue: missng XML code comments - parameterName: ShouldCreateMissingRevisionPaths type: Boolean - description: missng XML code comments + description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments @@ -73,11 +73,300 @@ categories: topics: - topic: notes path: /docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md - exists: false - markdown: '' + exists: true + markdown: >- + ## NodeBasePath Configuration ## + + The `NodeBasePaths` entry allows the filtering of the nodes to be replicated on the target projects. To try to explain the correct usage let us assume that we have a source team project `SourceProj` with the following node structures + + + - AreaPath + - SourceProj + - SourceProj\Team 1 + - SourceProj\Team 2 + - SourceProj\Team 2\Sub-Area + - SourceProj\Team 3 + - IterationPath + - SourceProj + - SourceProj\Sprint 1 + - SourceProj\Sprint 2 + - SourceProj\Sprint 2\Sub-Iteration + - SourceProj\Sprint 3 + + Depending upon what node structures you wish to migrate you would need the following settings. Exclusions are also possible by prefixing a path with an exclamation mark `!`. Example are + + + | | | + + |-|-| + + | Intention | Migrate all areas and iterations and all Work Items + + | NodeBasePath | `[]` + + | Comment | The same AreaPath and Iteration Paths are created on the target as on the source. Hence, all migrated WI remain in their existing area and iteration paths + + || + + | Intention | Only migrate area path `Team 2` and it associated Work Items, but all iteration paths + + | NodeBasePath | `["Team 2", "Sprint"]` + + | Comment | Only the area path ending `Team 2` will be migrated.
The `WIQLQueryBit` should be edited to limit the WI migrated to this area path e.g. add `AND [System.AreaPath] UNDER 'SampleProject\\Team 2'` .
The migrated WI will have an area path of `TargetProj\Team 2` but retain their iteration paths matching the sprint name on the source + + || + + | Intention | Only migrate iterations structure + + | NodeBasePath | `["Sprint"]` + + | Comment | Only the area path ending `Team 2` will be migrated
All the iteration paths will be migrated.
The migrated WI will have the default area path of `TargetProj` as their source area path was not migrated i.e. `TargetProj`
The migrated WI will have an iteration path match the sprint name on the source + + || + + | Intention | Move all WI to the existing area and iteration paths on the targetProj + + | NodeBasePath | `["DUMMY VALUE"]` + + | Comment | As the `NodeBasePath` does not match any source area or iteration path no nodes are migrated.
Migrated WI will be assigned to any matching area or iteration paths. If no matching ones can be found they will default to the respective root values + + || + + | Intention | Move the `Team 2` area, but not its `Sub-Area` + + | NodeBasePath | `["Team 2", "!Team 2\\SubArea"]` + + | Comment | The Work Items will have to be restricted to the right areas, e.g. with `AND [System.AreaPath] UNDER 'SampleProject\\Team 2' AND [System.AreaPath] NOT UNDER 'SampleProject\\Team 2\\Sub-Area'`, otherwise their migratin will fail + + + + # Iteration Maps and Area Maps + + + These two configuration elements apply after the `NodeBasePaths` selector, i.e. + + only on Areas and Iterations that have been selected for migration. They allow + + to change the area path, respectively the iteration path, of migrated work items. + + + These remapping rules are applied both while creating path nodes in the target + + project and when migrating work items. + + + These remapping rules are applied with a higher priority than the + + `PrefixProjectToNodes` option. This means that if no declared rule matches the + + path and the `PrefixProjectToNodes` option is enabled, then the old behavior is + + used. + + + The syntax is a dictionary of regular expressions and the replacement text. + + + *Warning*: These follow the + + [.net regular expression language](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference). + + The key in the dictionary is a regular expression search pattern, while the + + value is a regular expression replacement pattern. It is therefore possible to + + use back-references in the replacement string. + + + *Warning*: Special characters in the acceptation of regular expressions _and_ + + json both need to be escaped. For a key, this means, for example, that a + + literal backslash must be escaped for the regular expression language `\\` + + _and_ each of these backslashes must then be escaped for the json encoding: + + `\\\\`. In the replacement string, a literal `$` must be escaped with an + + additional `$` if it is followed by a number (due to the special meaning in + + regular expression replacement strings), while a backslash must be escaped + + (`\\`) due to the special meaning in json. + + + *Advice*: To avoid unexpected results, always match terminating backslashes in + + the search pattern and replacement string: if a search pattern ends with a + + backslash, you should also put one in the replacement string, and if the search + + pattern does not include a terminating backslash, then none should be included + + in the replacement string. + + + #### Examples explained + + + ```json + + "IterationMaps": { + "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", + "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", + "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", + }, + + "AreaMaps": { + "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", + "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", + } + + ``` + + + - `"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",` + + In an iteration path, `OriginalProject\Path1` found at the beginning of the + path, when followed by `\Sprint 2022`, will be replaced by + `TargetProject\AnotherPath\NewTeam`. + + `OriginalProject\Path1\Sprint 2022\Sprint 01` will become + `TargetProject\AnotherPath\NewTeam\Sprint 2022\Sprint 01` but + `OriginalProject\Path1\Sprint 2020\Sprint 03` will _not_ be transformed by + this rule. + + - `"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",` + + In an iteration path, `OriginalProject\Path1` found at the beginning of the + path, when followed by `\Sprint 2020`, will be replaced by + `TargetProject\AnotherPath\Archives\\Sprints 2020`. + + `OriginalProject\Path1\Sprint 2020\Sprint 01` will become + `TargetProject\AnotherPath\Archives\Sprint 2020\Sprint 01` but + `OriginalProject\Path1\Sprint 2021\Sprint 03` will _not_ be transformed by + this rule. + + - `"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2",` + + In an iteration path, `OriginalProject\Path2` will be replaced by + `TargetProject\YetAnotherPath\Path2`. + + - `"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",` + + In an area path, `OriginalProject\` found at the beginning of the path, when + followed by either `DescopeThis` or `DescopeThat` will be replaced by `TargetProject\Archive\Descoped\`. + + `OriginalProject\DescopeThis\Area` will be transformed to + `TargetProject\Archive\Descoped\DescopeThis\Area`. + `OriginalProject\DescopeThat\Product` will be transformed to + `TargetProject\Archive\Descoped\DescopeThat\Product`. + + - `"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\",` + + In an area path, `OriginalProject\` found at the beginning of the path will be + replaced by `TargetProject\NewArea\` unless it is followed by `DescopeThis` or + `DescopeThat`. + + `OriginalProject\ValidArea\` would be replaced by + `TargetProject\NewArea\ValidArea\` but `OriginalProject\DescopeThis` would not + be modified by this rule. + + ### More Complex Regex + + + Before your migration starts it will validate that all of the Areas and Iterations from the **Source** work items revisions exist on the **Target**. Any that do not exist will be flagged in the logs and if and the migration will stop just after it outputs a list of the missing nodes. + + + Our algorithm that converts the Source nodes to Target nodes processes the [mappings](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) at that time. This means that any valid mapped nodes will never be caught by the `This path is not anchored in the source project` message as they are already altered to be valid. + + + > We recently updated the logging for this part of the system to more easily debug both your mappings and to see what they system is doing with the nodes and their current state. You can set `"LogLevel": "Debug"` to see the details. + + + To add a mapping, you can follow [the documentation](https://nkdagility.com/learn/azure-devops-migration-tools/Reference/v1/Processors/WorkItemMigrationContext/#iteration-maps-and-area-maps) with this being the simplest way: + + + ``` + + "IterationMaps": { + "WorkItemMovedFromProjectName\\\\Iteration 1": "TargetProject\\Sprint 1", + }, + + "AreaMaps": { + "WorkItemMovedFromProjectName\\\\Team 2": "TargetProject\\ProductA\\Team 2", + } + + ``` + + Or you can use regular expressions to match the missing area or iteration paths: + + + ``` + + "IterationMaps": { + "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam", + "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020", + "^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2", + }, + + "AreaMaps": { + "^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\", + "^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\", + } + + ``` + + + If you want to use the matches in the replacement you can use the following: + + + ``` + + "IterationMaps": { + "^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1\$2", + } + + ``` + + If the olf iteration path was `\oldproject1\Custom Reporting\Sprint 13`, then this would result in a match for each Iteration node after the project node. You would then be able to reference any of the nodes using "$" and then the number of the match. + + + + Regular expressions are much more difficult to build and debug so it is a good idea to use a [regular expression tester](https://regex101.com/) to check that you are matching the right things and to build them in ChatGTP. + + + _NOTE: You need `\\` to escape a `\` the pattern, and `\\` to escape a `\` in JSON. Therefor on the left of the match you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the match_ + + + ![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) + + + ### Example with AreaMaps and IterationMaps + + + ``` + + "CommonEnrichersConfig": [ + { + "$type": "TfsNodeStructureOptions", + "PrefixProjectToNodes": false, + "NodeBasePaths": [], + "AreaMaps": { + "^Skypoint Cloud$" : "MigrationTest5" + }, + "IterationMaps": { + "^Skypoint Cloud\\\\Sprint 1$" : "MigrationTest5\\Sprint 1" + }, + "ShouldCreateMissingRevisionPaths": true, + "ReplicateAllExistingNodes": true + } + ], + + ``` - topic: introduction path: /docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-introduction.md - exists: false + exists: true markdown: '' --- \ No newline at end of file diff --git a/docs/collections/_reference/reference.v2.processorenrichers.tfsrevisionmanager.md b/docs/collections/_reference/reference.v2.processorenrichers.tfsrevisionmanager.md index 67be28ca0..962292fb8 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.tfsrevisionmanager.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.tfsrevisionmanager.md @@ -8,31 +8,31 @@ configurationSamples: { "$type": "TfsRevisionManagerOptions", "Enabled": true, - "ReplayRevisions": false, + "ReplayRevisions": true, "MaxRevisions": 0 } sampleFor: MigrationTools.Enrichers.TfsRevisionManagerOptions -description: missng XML code comments +description: The TfsRevisionManager manipulates the revisions of a work item to reduce the number of revisions that are migrated. className: TfsRevisionManager typeName: ProcessorEnrichers architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: MaxRevisions type: Int32 - description: missng XML code comments - defaultValue: missng XML code comments + description: Sets the maximum number of revisions that will be migrated. "First + Last N = Max". If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. + defaultValue: 0 - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: ReplayRevisions type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments + description: You can choose to migrate the tip only (a single write) or all of the revisions (many writes). If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. + defaultValue: true status: missng XML code comments processingTarget: missng XML code comments classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs diff --git a/docs/collections/_reference/reference.v2.processorenrichers.tfsvalidaterequiredfield.md b/docs/collections/_reference/reference.v2.processorenrichers.tfsvalidaterequiredfield.md index 1c88e65b5..93ec62034 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.tfsvalidaterequiredfield.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.tfsvalidaterequiredfield.md @@ -17,11 +17,11 @@ architecture: v2 options: - parameterName: Enabled type: Boolean - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments - parameterName: RefName type: String - description: missng XML code comments + description: For internal use defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments diff --git a/docs/collections/_reference/reference.v2.processorenrichers.tfsworkitemlinkenricher.md b/docs/collections/_reference/reference.v2.processorenrichers.tfsworkitemlinkenricher.md index 747e51969..c5d47d42d 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.tfsworkitemlinkenricher.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.tfsworkitemlinkenricher.md @@ -1,16 +1,42 @@ --- -optionsClassName: -optionsClassFullName: -configurationSamples: [] +optionsClassName: TfsWorkItemLinkEnricherOptions +optionsClassFullName: MigrationTools.Enrichers.TfsWorkItemLinkEnricherOptions +configurationSamples: +- name: default + description: + code: >- + { + "$type": "TfsWorkItemLinkEnricherOptions", + "Enabled": true, + "FilterIfLinkCountMatches": true, + "SaveAfterEachLinkIsAdded": false + } + sampleFor: MigrationTools.Enrichers.TfsWorkItemLinkEnricherOptions description: missng XML code comments className: TfsWorkItemLinkEnricher typeName: ProcessorEnrichers architecture: v2 -options: [] +options: +- parameterName: Enabled + type: Boolean + description: For internal use + defaultValue: missng XML code comments +- parameterName: FilterIfLinkCountMatches + type: Boolean + description: Skip validating links if the number of links in the source and the target matches! + defaultValue: missng XML code comments +- parameterName: RefName + type: String + description: For internal use + defaultValue: missng XML code comments +- parameterName: SaveAfterEachLinkIsAdded + type: Boolean + description: Save the work item after each link is added. This will slow the migration as it will cause many saves to the TFS database. + defaultValue: missng XML code comments status: missng XML code comments processingTarget: missng XML code comments -classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/Enrichers/TfsWorkItemLinkEnricher.cs -optionsClassFile: +classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricher.cs +optionsClassFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs redirectFrom: [] layout: reference diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs index b54c2e506..7e470e065 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs @@ -37,6 +37,9 @@ public struct TfsNodeStructureSettings public Dictionary FoundNodes; } + /// + /// The TfsNodeStructureEnricher is used to create missing nodes in the target project. To configure it add a `TfsNodeStructureOptions` section to `CommonEnrichersConfig` in the config file. Otherwise defaults will be applied. + /// public class TfsNodeStructure : WorkItemProcessorEnricher { private readonly Dictionary _pathToKnownNodeMap = new Dictionary(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs index 220d4ef85..621902369 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Microsoft.TeamFoundation.Build.Client; namespace MigrationTools.Enrichers { @@ -7,21 +8,55 @@ public class TfsNodeStructureOptions : ProcessorEnricherOptions, ITfsNodeStructu { public override Type ToConfigure => typeof(TfsNodeStructure); + + /// + /// Prefix the nodes with the new project name. + /// + /// false public bool PrefixProjectToNodes { get; set; } + + /// + /// The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) + /// + /// ["/"] public string[] NodeBasePaths { get; set; } + + /// + /// Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, + /// that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + /// + /// {} public Dictionary AreaMaps { get; set; } + + /// + /// Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, + /// that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. + /// + /// {} public Dictionary IterationMaps { get; set; } + + /// + /// When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. + /// public bool ShouldCreateMissingRevisionPaths { get; set; } public bool ReplicateAllExistingNodes { get; set; } public override void SetDefaults() { Enabled = true; + PrefixProjectToNodes = false; AreaMaps = new Dictionary(); IterationMaps = new Dictionary(); ShouldCreateMissingRevisionPaths = true; ReplicateAllExistingNodes = false; } + + static public TfsNodeStructureOptions GetDefaults() + { + var result = new TfsNodeStructureOptions(); + result.SetDefaults(); + return result; + } } public interface ITfsNodeStructureOptions diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs index d7acf8868..418fc243a 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManager.cs @@ -14,6 +14,9 @@ namespace MigrationTools.Enrichers { + /// + /// The TfsRevisionManager manipulates the revisions of a work item to reduce the number of revisions that are migrated. + /// public class TfsRevisionManager : WorkItemProcessorEnricher { public TfsRevisionManager(IServiceProvider services, ILogger logger) @@ -21,7 +24,7 @@ public TfsRevisionManager(IServiceProvider services, ILogger { } - private TfsRevisionManagerOptions Options { get; set; } + public TfsRevisionManagerOptions Options { get; private set;} [Obsolete("Old v1 arch: this is a v2 class", true)] public override void Configure(bool save = true, bool filter = true) diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManagerOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManagerOptions.cs index cd41a783b..ddf8243b2 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManagerOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsRevisionManagerOptions.cs @@ -6,12 +6,32 @@ public class TfsRevisionManagerOptions : ProcessorEnricherOptions { public override Type ToConfigure => typeof(TfsRevisionManager); + /// + /// You can choose to migrate the tip only (a single write) or all of the revisions (many writes). + /// If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. + /// + /// true public bool ReplayRevisions { get; set; } + + /// + /// Sets the maximum number of revisions that will be migrated. "First + Last N = Max". + /// If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. + /// + /// 0 public int MaxRevisions { get; set; } public override void SetDefaults() { Enabled = true; + ReplayRevisions = true; + MaxRevisions = 0; + } + + static public TfsRevisionManagerOptions GetDefaults() + { + var result = new TfsRevisionManagerOptions(); + result.SetDefaults(); + return result; } } } \ No newline at end of file diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Enrichers/TfsWorkItemLinkEnricher.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricher.cs similarity index 93% rename from src/MigrationTools.Clients.AzureDevops.ObjectModel/Enrichers/TfsWorkItemLinkEnricher.cs rename to src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricher.cs index f70014645..f8ba928af 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Enrichers/TfsWorkItemLinkEnricher.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricher.cs @@ -5,6 +5,7 @@ using Microsoft.TeamFoundation.WorkItemTracking.Client; using MigrationTools._EngineV1.Clients; using MigrationTools.DataContracts; +using MigrationTools.Enrichers; using MigrationTools.Exceptions; using MigrationTools.Processors; @@ -12,23 +13,19 @@ namespace MigrationTools.Enrichers { public class TfsWorkItemLinkEnricher : WorkItemProcessorEnricher { - private bool _save = true; - private bool _filterWorkItemsThatAlreadyExistInTarget = true; private IMigrationEngine Engine; + public TfsWorkItemLinkEnricherOptions Options { get; private set; } + public TfsWorkItemLinkEnricher(IServiceProvider services, ILogger logger) : base(services, logger) { Engine = services.GetRequiredService(); } - [Obsolete] - public override void Configure( - bool save = true, - bool filterWorkItemsThatAlreadyExistInTarget = true) + public override void Configure(IProcessorEnricherOptions options) { - _save = save; - _filterWorkItemsThatAlreadyExistInTarget = filterWorkItemsThatAlreadyExistInTarget; + Options = (TfsWorkItemLinkEnricherOptions)options; } [Obsolete] @@ -50,7 +47,7 @@ public override int Enrich(WorkItemData sourceWorkItemLinkStart, WorkItemData ta if (ShouldCopyLinks(sourceWorkItemLinkStart, targetWorkItemLinkStart)) { - Log.LogDebug("Links = '{@sourceWorkItemLinkStartLinks}", sourceWorkItemLinkStart.Links); + Log.LogTrace("Links = '{@sourceWorkItemLinkStartLinks}", sourceWorkItemLinkStart.Links); foreach (Link item in sourceWorkItemLinkStart.ToWorkItem().Links) { try @@ -136,7 +133,7 @@ public void MigrateSharedSteps(WorkItemData wiSourceL, WorkItemData wiTargetL) } } - if (wiTargetL.ToWorkItem().IsDirty && _save) + if (wiTargetL.ToWorkItem().IsDirty && Options.SaveAfterEachLinkIsAdded) { wiTargetL.SaveToAzureDevOps(); } @@ -168,7 +165,7 @@ public void MigrateSharedParameters(WorkItemData wiSourceL, WorkItemData wiTarge } } - if (wiTargetL.ToWorkItem().IsDirty && _save) + if (wiTargetL.ToWorkItem().IsDirty && Options.SaveAfterEachLinkIsAdded) { wiTargetL.SaveToAzureDevOps(); } @@ -177,7 +174,7 @@ public void MigrateSharedParameters(WorkItemData wiSourceL, WorkItemData wiTarge private void CreateExternalLink(ExternalLink sourceLink, WorkItemData target) { var exist = (from Link l in target.ToWorkItem().Links - where l is ExternalLink && ((ExternalLink)l).LinkedArtifactUri == ((ExternalLink)sourceLink).LinkedArtifactUri + where l is ExternalLink && ((ExternalLink)l).LinkedArtifactUri == sourceLink.LinkedArtifactUri select (ExternalLink)l).SingleOrDefault(); if (exist == null) { @@ -191,7 +188,7 @@ private void CreateExternalLink(ExternalLink sourceLink, WorkItemData target) // DevOps doesn't seem to take impersonation very nicely here - as of 13.05.22 the following line would get you an error: // System.FormatException: The string 'Microsoft.TeamFoundation.WorkItemTracking.Common.ServerDefaultFieldValue' is not a valid AllXsd value. // target.ToWorkItem().Fields["System.ModifiedBy"].Value = "Migration"; - if (_save) + if (Options.SaveAfterEachLinkIsAdded) { try { @@ -200,7 +197,7 @@ private void CreateExternalLink(ExternalLink sourceLink, WorkItemData target) catch (Exception ex) { // Ignore this link because the TFS server didn't recognize its type (There's no point in crashing the rest of the migration due to a link) - if(ex.Message.Contains("Unrecognized Resource link")) + if (ex.Message.Contains("Unrecognized Resource link")) { Log.LogError(ex, "[{ExceptionType}] Failed to save link {SourceLinkType} on {TargetId}", ex.GetType().Name, sourceLink.GetType().Name, target.Id); // Remove the link from the target so it doesn't cause problems downstream @@ -238,7 +235,7 @@ private void CreateRelatedLink(WorkItemData wiSourceL, RelatedLink item, WorkIte WorkItemData wiSourceR = null; WorkItemData wiTargetR = null; - Log.LogDebug("RelatedLink is of ArtifactLinkType='{ArtifactLinkType}':LinkTypeEnd='{LinkTypeEndImmutableName}' on WorkItemId s:{ids} t:{idt}", rl.ArtifactLinkType.Name, rl.LinkTypeEnd == null? "null" : rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiTargetL.Id); + Log.LogDebug("RelatedLink is of ArtifactLinkType='{ArtifactLinkType}':LinkTypeEnd='{LinkTypeEndImmutableName}' on WorkItemId s:{ids} t:{idt}", rl.ArtifactLinkType.Name, rl.LinkTypeEnd == null ? "null" : rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiTargetL.Id); if (rl.LinkTypeEnd != null) // On a registered link type these will for sure fail as target is not in the system. { @@ -261,7 +258,7 @@ private void CreateRelatedLink(WorkItemData wiSourceL, RelatedLink item, WorkIte return; } } - + if (wiTargetR != null) { bool IsExisting = false; @@ -273,11 +270,11 @@ where l is RelatedLink && ((RelatedLink)l).RelatedWorkItemId.ToString() == wiTargetR.Id && ((RelatedLink)l).LinkTypeEnd.ImmutableName == item.LinkTypeEnd.ImmutableName select (RelatedLink)l).SingleOrDefault(); - IsExisting = (exist != null); + IsExisting = exist != null; } catch (Exception ex) { - Log.LogError(ex, " [SKIP] Unable to migrate links where wiSourceL={0}, wiSourceR={1}, wiTargetL={2}", ((wiSourceL != null) ? wiSourceL.Id.ToString() : "NotFound"), ((wiSourceR != null) ? wiSourceR.Id.ToString() : "NotFound"), ((wiTargetL != null) ? wiTargetL.Id.ToString() : "NotFound")); + Log.LogError(ex, " [SKIP] Unable to migrate links where wiSourceL={0}, wiSourceR={1}, wiTargetL={2}", wiSourceL != null ? wiSourceL.Id.ToString() : "NotFound", wiSourceR != null ? wiSourceR.Id.ToString() : "NotFound", wiTargetL != null ? wiTargetL.Id.ToString() : "NotFound"); return; } @@ -334,7 +331,7 @@ where l is RelatedLink // DevOps doesn't seem to take impersonation very nicely here - as of 13.05.22 the following line would get you an error: // System.FormatException: The string 'Microsoft.TeamFoundation.WorkItemTracking.Common.ServerDefaultFieldValue' is not a valid AllXsd value. // wiTargetL.ToWorkItem().Fields["System.ModifiedBy"].Value = "Migration"; - if (_save) + if (Options.SaveAfterEachLinkIsAdded) { wiTargetL.SaveToAzureDevOps(); } @@ -364,7 +361,7 @@ where l is RelatedLink } else { - Log.LogWarning("[SKIP] [LINK_CAPTURE_RELATED] [{RegisteredLinkType}] target not found. wiSourceL={wiSourceL}, wiSourceR={wiSourceR}, wiTargetL={wiTargetL}", rl.ArtifactLinkType.GetType().Name, wiSourceL == null ? "null" : wiSourceL.Id , wiSourceR == null ? "null" : wiSourceR.Id, wiTargetL == null? "null": wiTargetL.Id); + Log.LogWarning("[SKIP] [LINK_CAPTURE_RELATED] [{RegisteredLinkType}] target not found. wiSourceL={wiSourceL}, wiSourceR={wiSourceR}, wiTargetL={wiTargetL}", rl.ArtifactLinkType.GetType().Name, wiSourceL == null ? "null" : wiSourceL.Id, wiSourceR == null ? "null" : wiSourceR.Id, wiTargetL == null ? "null" : wiTargetL.Id); } } @@ -428,7 +425,7 @@ where string.Equals(sourceLinkAbsoluteUri, absoluteUri, StringComparison.Ordinal // DevOps doesn't seem to take impersonation very nicely here - as of 13.05.22 the following line would get you an error: // System.FormatException: The string 'Microsoft.TeamFoundation.WorkItemTracking.Common.ServerDefaultFieldValue' is not a valid AllXsd value. // target.ToWorkItem().Fields["System.ModifiedBy"].Value = "Migration"; - if (_save) + if (Options.SaveAfterEachLinkIsAdded) { target.SaveToAzureDevOps(); } @@ -449,7 +446,7 @@ private string GetAbsoluteUri(Hyperlink hyperlink) private bool ShouldCopyLinks(WorkItemData sourceWorkItemLinkStart, WorkItemData targetWorkItemLinkStart) { - if (_filterWorkItemsThatAlreadyExistInTarget) + if (Options.FilterIfLinkCountMatches) { if (targetWorkItemLinkStart.ToWorkItem().Links.Count == sourceWorkItemLinkStart.ToWorkItem().Links.Count) // we should never have this as the target should not have existed in this path { @@ -465,12 +462,6 @@ private bool IsHyperlink(Link item) return item is Hyperlink; } - [Obsolete("v2 Archtecture: use Configure(bool save = true, bool filter = true) instead", true)] - public override void Configure(IProcessorEnricherOptions options) - { - throw new NotImplementedException(); - } - protected override void RefreshForProcessorType(IProcessor processor) { throw new NotImplementedException(); diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs new file mode 100644 index 000000000..aadc05ea5 --- /dev/null +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Microsoft.TeamFoundation.Build.Client; + +namespace MigrationTools.Enrichers +{ + public class TfsWorkItemLinkEnricherOptions : ProcessorEnricherOptions, ITfsWorkItemLinkEnricherOptions + { + public override Type ToConfigure => typeof(TfsNodeStructure); + + /// + /// Skip validating links if the number of links in the source and the target matches! + /// + public bool FilterIfLinkCountMatches { get; set; } + + + /// + /// Save the work item after each link is added. This will slow the migration as it will cause many saves to the TFS database. + /// + public bool SaveAfterEachLinkIsAdded { get; set; } + + + public override void SetDefaults() + { + Enabled = true; + FilterIfLinkCountMatches = true; + SaveAfterEachLinkIsAdded = false; + } + + static public TfsNodeStructureOptions GetDefaults() + { + var result = new TfsNodeStructureOptions(); + result.SetDefaults(); + return result; + } + } + + public interface ITfsWorkItemLinkEnricherOptions + { + public bool FilterIfLinkCountMatches { get; set; } + public bool SaveAfterEachLinkIsAdded { get; set; } + } +} \ No newline at end of file diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWiqlDefinition.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWiqlDefinition.cs deleted file mode 100644 index 2486cea8a..000000000 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWiqlDefinition.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MigrationTools._EngineV1.Clients -{ - public class TfsWiqlDefinition - { - public string QueryBit { get; set; } - public string OrderBit { get; set; } - } -} \ No newline at end of file diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs index da22b3e60..4356482bc 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/_Enginev1/Clients/TfsWorkItemMigrationClient.cs @@ -36,22 +36,16 @@ public TfsWorkItemMigrationClient(IWorkItemQueryBuilderFactory workItemQueryBuil public List FilterExistingWorkItems( List sourceWorkItems, - TfsWiqlDefinition wiqlDefinition, + string query, TfsWorkItemMigrationClient sourceWorkItemMigrationClient) { Log.Debug("FilterExistingWorkItems: START | "); - var targetQuery = - string.Format( - @"SELECT [System.Id], [{0}] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {1} ORDER BY {2}", - _config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName, - wiqlDefinition.QueryBit, - wiqlDefinition.OrderBit); Log.Debug("FilterByTarget: Query Execute..."); - var targetFoundItems = GetWorkItems(targetQuery); + var targetFoundItems = GetWorkItems(query); Log.Debug("FilterByTarget: ... query complete."); - Log.Debug("FilterByTarget: Found {TargetWorkItemCount} based on the WIQLQueryBit in the target system.", targetFoundItems.Count); + Log.Debug("FilterByTarget: Found {TargetWorkItemCount} based on the WIQLQuery in the target system.", targetFoundItems.Count); var targetFoundIds = (from WorkItemData twi in targetFoundItems select GetReflectedWorkItemId(twi)) //exclude null IDs .Where(x=> x != null) @@ -217,6 +211,7 @@ private Endpoints.IWorkItemQuery GetWorkItemQuery(string WIQLQuery) var wiqb = _workItemQueryBuilderFactory.Create(); wiqb.Query = WIQLQuery; wiqb.AddParameter("TeamProject", MigrationClient.Config.AsTeamProjectConfig().Project); + wiqb.AddParameter("ReflectedWorkItemIdFieldName", MigrationClient.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); return wiqb.BuildWIQLQuery(MigrationClient); } diff --git a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessor.cs b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessor.cs index 0f8468ee4..fc7c23915 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessor.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessor.cs @@ -38,9 +38,9 @@ public override void Configure(IProcessorOptions options) base.Configure(options); Log.LogInformation("AzureDevOpsPipelineProcessor::Configure"); _options = (KeepOutboundLinkTargetProcessorOptions)options; - if (string.IsNullOrEmpty(_options.WIQLQueryBit)) + if (string.IsNullOrEmpty(_options.WIQLQuery)) { - throw new Exception($"The {nameof(_options.WIQLQueryBit)} needs to be set"); + throw new Exception($"The {nameof(_options.WIQLQuery)} needs to be set"); } } @@ -74,7 +74,7 @@ private void EnsureConfigured() private async Task AddLinksToWorkItems() { var wiqlClient = Source.GetHttpClient("wit/wiql"); - WiqlResponse workItems = await GetWorkItemsBasedOnWiql(wiqlClient, _options.WIQLQueryBit); + WiqlResponse workItems = await GetWorkItemsBasedOnWiql(wiqlClient, _options.WIQLQuery); var client = Source.GetHttpClient("wit/workitemsbatch"); diff --git a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessorOptions.cs b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessorOptions.cs index 2e262a9e5..db6da7bfe 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessorOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/KeepOutboundLinkTargetProcessorOptions.cs @@ -7,7 +7,7 @@ public class KeepOutboundLinkTargetProcessorOptions : ProcessorOptions { public override Type ToConfigure => typeof(KeepOutboundLinkTargetProcessor); - public string WIQLQueryBit { get; set; } + public string WIQLQuery { get; set; } public string TargetLinksToKeepOrganization { get; set; } public string TargetLinksToKeepProject { get; set; } @@ -24,7 +24,7 @@ public override IProcessorOptions GetDefault() public override void SetDefaults() { - WIQLQueryBit = "Select [System.Id] From WorkItems Where [System.TeamProject] = @project and not [System.WorkItemType] contains 'Test Suite, Test Plan,Shared Steps,Shared Parameter,Feedback Request'"; + 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 = Guid.NewGuid().ToString(); DryRun = true; diff --git a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessor.cs b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessor.cs index 2042c267c..703f6801d 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessor.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessor.cs @@ -35,9 +35,9 @@ public override void Configure(IProcessorOptions options) base.Configure(options); Log.LogInformation("AzureDevOpsPipelineProcessor::Configure"); _options = (OutboundLinkCheckingProcessorOptions)options; - if(string.IsNullOrEmpty(_options.WIQLQueryBit)) + if(string.IsNullOrEmpty(_options.WIQLQuery)) { - throw new Exception($"The {nameof(_options.WIQLQueryBit)} needs to be set"); + throw new Exception($"The {nameof(_options.WIQLQuery)} needs to be set"); } if (string.IsNullOrEmpty(_options.ResultFileName)) { @@ -77,7 +77,7 @@ private async Task FindAllOrgsAndProjects() var wiqlClient = Source.GetHttpClient("wit/wiql"); var query = new WiqlRequest { - Query = _options.WIQLQueryBit + Query = _options.WIQLQuery }; var wiqlResult = await wiqlClient.PostAsJsonAsync("", query); diff --git a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessorOptions.cs b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessorOptions.cs index aebfb6699..1a3d41225 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessorOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.Rest/Processors/OutboundLinkCheckingProcessorOptions.cs @@ -7,7 +7,7 @@ public class OutboundLinkCheckingProcessorOptions : ProcessorOptions { public override Type ToConfigure => typeof(OutboundLinkCheckingProcessor); - public string WIQLQueryBit { get; set; } + public string WIQLQuery { get; set; } public string ResultFileName { get; set; } public override IProcessorOptions GetDefault() @@ -18,7 +18,7 @@ public override IProcessorOptions GetDefault() public override void SetDefaults() { - WIQLQueryBit = "Select [System.Id] From WorkItems Where [System.TeamProject] = @project and not [System.WorkItemType] contains 'Test Suite, Test Plan,Shared Steps,Shared Parameter,Feedback Request'"; + 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'"; ResultFileName = "c:/temp/OutboundLinks.csv"; } } diff --git a/src/MigrationTools.Samples/configuration.json b/src/MigrationTools.Samples/configuration.json index c3a229cd8..d1b61c00e 100644 --- a/src/MigrationTools.Samples/configuration.json +++ b/src/MigrationTools.Samples/configuration.json @@ -117,8 +117,7 @@ { "ObjectType": "WorkItemDeleteConfig", "Enabled": true, - "QueryBit": "AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - "OrderBit": "[System.ChangedDate] desc" + "Query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", }, { "ObjectType": "WorkItemMigrationConfig", @@ -128,8 +127,7 @@ "UpdateCreatedBy": true, "BuildFieldTable": false, "AppendMigrationToolSignatureFooter": false, - "QueryBit": "AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", - "OrderBit": "[System.ChangedDate] desc", + "WiqlQuery": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", "Enabled": true, "LinkMigration": true, "AttachmentMigration": true, diff --git a/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json b/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json index 86d81cbd4..b343901a9 100644 --- a/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json +++ b/src/MigrationTools.Samples/demo-mapping-scrum2Agile.json @@ -1,62 +1,65 @@ { - "TelemetryEnableTrace": true, - "Target": { - "Collection": "https://tfs.test.company.com/tfs/col2/", - "Name": "ProjectName" + "TelemetryEnableTrace": true, + "Target": { + "Collection": "https://tfs.test.company.com/tfs/col2/", + "Name": "ProjectName" + }, + "ReflectedWorkItemIDFieldName": "TfsMigrationTool.ReflectedWorkItemId", + "WorkItemTypeDefinition": { + "Bug": "Bug", + "User Story": "User Story", + "Requirement": "Requirement", + "Task": "Task", + "Test Case": "Test Case", + "Shared Steps": "Shared Steps", + "Shared Parameter": "Shared Parameter" + }, + "FieldMaps": [ + { + "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoTagMapConfig", + "WorkItemTypeName": "*", + "sourceField": "System.State", + "formatExpression": "OriginalState:{0}" }, - "ReflectedWorkItemIDFieldName": "TfsMigrationTool.ReflectedWorkItemId", - "WorkItemTypeDefinition": { - "Bug": "Bug", - "User Story": "User Story", - "Requirement": "Requirement", - "Task": "Task", - "Test Case": "Test Case", - "Shared Steps": "Shared Steps", - "Shared Parameter": "Shared Parameter" + { + "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldValueMapConfig", + "WorkItemTypeName": "*", + "sourceField": "System.State", + "targetField": "System.State", + "valueMapping": { + "Approved": "New", + "New": "New", + "Committed": "Active", + "In Progress": "Active", + "To Do": "New", + "Done": "Closed" + } }, - "FieldMaps": [{ - "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoTagMapConfig", - "WorkItemTypeName": "*", - "sourceField": "System.State", - "formatExpression": "OriginalState:{0}" - }, - { - "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldValueMapConfig", - "WorkItemTypeName": "*", - "sourceField": "System.State", - "targetField": "System.State", - "valueMapping": { - "Approved": "New", - "New": "New", - "Committed": "Active", - "In Progress": "Active", - "To Do": "New", - "Done": "Closed" - } - }, - { - "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoTagMapConfig", - "WorkItemTypeName": "*", - "sourceField": "Microsoft.VSTS.Common.BusinessValue", - "formatExpression": "BV:{0}" - }, - { - "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoFieldMapConfig", - "WorkItemTypeName": "*", - "sourceField": "Microsoft.VSTS.Common.BacklogPriority", - "targetField": "Microsoft.VSTS.Common.StackRank" - }, - { - "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoFieldMapConfig", - "WorkItemTypeName": "*", - "sourceField": "Microsoft.VSTS.Scheduling.Effort", - "targetField": "Microsoft.VSTS.Scheduling.StoryPoints" - } - ], - "Processors": [{ - "ObjectType": "WorkItemUpdateConfig", - "WhatIf": false, - "Disabled": false, - "QueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" - }] + { + "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoTagMapConfig", + "WorkItemTypeName": "*", + "sourceField": "Microsoft.VSTS.Common.BusinessValue", + "formatExpression": "BV:{0}" + }, + { + "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoFieldMapConfig", + "WorkItemTypeName": "*", + "sourceField": "Microsoft.VSTS.Common.BacklogPriority", + "targetField": "Microsoft.VSTS.Common.StackRank" + }, + { + "ObjectType": "VstsSyncMigrator.Engine.Configuration.FieldMap.FieldtoFieldMapConfig", + "WorkItemTypeName": "*", + "sourceField": "Microsoft.VSTS.Scheduling.Effort", + "targetField": "Microsoft.VSTS.Scheduling.StoryPoints" + } + ], + "Processors": [ + { + "ObjectType": "WorkItemUpdateConfig", + "WhatIf": false, + "Disabled": false, + "Query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" + } + ] } \ No newline at end of file diff --git a/src/MigrationTools.Samples/demo-migration-reset.json b/src/MigrationTools.Samples/demo-migration-reset.json index e6dd9110a..73d503edb 100644 --- a/src/MigrationTools.Samples/demo-migration-reset.json +++ b/src/MigrationTools.Samples/demo-migration-reset.json @@ -23,6 +23,6 @@ "ObjectType": "WorkItemUpdateConfig", "WhatIf": false, "Enabled": true, - "QueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] <> '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" + "Query": "AND [TfsMigrationTool.ReflectedWorkItemId] <> '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" }] } \ No newline at end of file diff --git a/src/MigrationTools.Samples/demo-migration.json b/src/MigrationTools.Samples/demo-migration.json index 704583173..3c2bf6f3a 100644 --- a/src/MigrationTools.Samples/demo-migration.json +++ b/src/MigrationTools.Samples/demo-migration.json @@ -26,20 +26,20 @@ "ObjectType": "NodeStructuresMigrationConfig", "Enabled": false }, - { - "ObjectType": "WorkItemMigrationConfig", - "Enabled": false, - "QueryBit": "AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" - }, + { + "ObjectType": "WorkItemMigrationConfig", + "Enabled": false, + "Query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')" + }, { "ObjectType": "LinkMigrationConfig", "Enabled": false }, - { - "ObjectType": "AttachementExportMigrationConfig", - "Enabled": false, - "QueryBit": "AND [System.AttachedFileCount] > 0" - }, + { + "ObjectType": "AttachementExportMigrationConfig", + "Enabled": false, + "Query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AttachedFileCount] > 0" + }, { "ObjectType": "AttachementImportMigrationConfig", "Enabled": false diff --git a/src/MigrationTools/ProcessorEnrichers/ProcessorEnricherOptions.cs b/src/MigrationTools/ProcessorEnrichers/ProcessorEnricherOptions.cs index 0b2f48dd7..e140c27bc 100644 --- a/src/MigrationTools/ProcessorEnrichers/ProcessorEnricherOptions.cs +++ b/src/MigrationTools/ProcessorEnrichers/ProcessorEnricherOptions.cs @@ -4,9 +4,16 @@ namespace MigrationTools.Enrichers { public abstract class ProcessorEnricherOptions : IProcessorEnricherOptions { + /// + /// For internal use + /// public bool Enabled { get; set; } public abstract Type ToConfigure { get; } + + /// + /// For internal use + /// public string RefName { get; set; } public abstract void SetDefaults(); diff --git a/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs b/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs index ac9abd078..ac89adff0 100644 --- a/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs +++ b/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs @@ -126,6 +126,7 @@ public EngineConfiguration BuildReference() public EngineConfiguration BuildGettingStarted() { EngineConfiguration ec = CreateEmptyConfig(); + ec.CommonEnrichersConfig = new List(); AddWorkItemMigrationDefault(ec); return ec; } diff --git a/src/MigrationTools/_EngineV1/Configuration/IWorkItemProcessorConfig.cs b/src/MigrationTools/_EngineV1/Configuration/IWorkItemProcessorConfig.cs index fe9cf17f8..266d259d4 100644 --- a/src/MigrationTools/_EngineV1/Configuration/IWorkItemProcessorConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/IWorkItemProcessorConfig.cs @@ -4,10 +4,8 @@ namespace MigrationTools._EngineV1.Configuration { public interface IWorkItemProcessorConfig : IProcessorConfig { - public string WIQLQueryBit { get; set; } + public string WIQLQuery { get; set; } - /// - public string WIQLOrderBit { get; set; } public IList WorkItemIDs { get; set; } public bool FilterWorkItemsThatAlreadyExistInTarget { get; set; } diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/FixGitCommitLinksConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/FixGitCommitLinksConfig.cs index c0ce6bffa..197793608 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/FixGitCommitLinksConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/FixGitCommitLinksConfig.cs @@ -6,10 +6,7 @@ public class FixGitCommitLinksConfig : IProcessorConfig { public string TargetRepository { get; set; } public bool Enabled { get; set; } - public string QueryBit { get; set; } - - /// - public string OrderBit { get; set; } + public string Query { get; set; } public string Processor { diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/TestPlansAndSuitesMigrationConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/TestPlansAndSuitesMigrationConfig.cs index f17eaba97..44b91f75d 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/TestPlansAndSuitesMigrationConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/TestPlansAndSuitesMigrationConfig.cs @@ -4,11 +4,7 @@ namespace MigrationTools._EngineV1.Configuration.Processing { public class TestPlansAndSuitesMigrationConfig : IProcessorConfig { - /// - /// Prefix the nodes with the new project name. - /// - /// false - public bool PrefixProjectToNodes { get; set; } + public bool Enabled { get; set; } /// @@ -21,7 +17,7 @@ public class TestPlansAndSuitesMigrationConfig : IProcessorConfig /// Filtering conditions to decide whether to migrate a test plan or not. When provided, this partial query is added after `Select * From TestPlan Where` when selecting test plans. Among filtering options, `AreaPath`, `PlanName` and `PlanState` are known to work. There is unfortunately no documentation regarding the available fields. /// /// `String.Empty` - public string TestPlanQueryBit { get; set; } + public string TestPlanQuery { get; set; } /// /// ??Not sure what this does. Check code. @@ -39,25 +35,6 @@ public class TestPlansAndSuitesMigrationConfig : IProcessorConfig /// Indicates whether the configuration for node structure transformation should be taken from the common enricher configs. Otherwise the configuration elements below are used /// /// false - public bool UseCommonNodeStructureEnricherConfig { get; set; } - - /// - /// See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - /// - /// [] - public string[] NodeBasePaths { get; set; } - - /// - /// See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - /// - /// null - public Dictionary AreaMaps { get; set; } - - /// - /// See documentation for [NodeStructure](/docs/Reference/v1/Processors/WorkItemMigrationConfig.md) - /// - /// null - public Dictionary IterationMaps { get; set; } public string Processor { diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemDeleteConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemDeleteConfig.cs index cb830ddb6..eb4872120 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemDeleteConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemDeleteConfig.cs @@ -14,12 +14,10 @@ public string Processor public WorkItemDeleteConfig() { Enabled = false; - WIQLQueryBit = @"AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - WIQLOrderBit = "[System.ChangedDate] desc"; + 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"; } - public string WIQLQueryBit { get; set; } - public string WIQLOrderBit { get; set; } + public string WIQLQuery { get; set; } public IList WorkItemIDs { get; set; } public bool FilterWorkItemsThatAlreadyExistInTarget { get; set; } public bool PauseAfterEachWorkItem { get; set; } diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemMigrationConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemMigrationConfig.cs index b6b6a0112..001a1fc11 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemMigrationConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemMigrationConfig.cs @@ -5,19 +5,7 @@ namespace MigrationTools._EngineV1.Configuration.Processing public class WorkItemMigrationConfig : IWorkItemProcessorConfig { - /// - /// You can choose to migrate the tip only (a single write) or all of the revisions (many writes). - /// If you are setting this to `false` to migrate only the tip then you should set `BuildFieldTable` to `true`. - /// - /// true - public bool ReplayRevisions { get; set; } - - /// - /// Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - /// - /// false - public bool PrefixProjectToNodes { get; set; } - + /// /// 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, @@ -37,14 +25,8 @@ public class WorkItemMigrationConfig : IWorkItemProcessorConfig /// /// 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) /// - /// AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - public string WIQLQueryBit { get; set; } - - /// - /// A work item query to affect the order in which the work items are migrated. Don't leave this empty. - /// - /// [System.ChangedDate] desc - public string WIQLOrderBit { get; set; } + /// 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 + public string WIQLQuery { get; set; } /// /// If enabled then the processor will run @@ -58,12 +40,6 @@ public class WorkItemMigrationConfig : IWorkItemProcessorConfig /// ? public string Processor => "WorkItemMigrationContext"; - /// - /// If enabled this will migrate the Links for the work item at the same time as the whole work item. - /// - /// true - public bool LinkMigration { get; set; } - /// /// If enabled this will migrate all of the attachments at the same time as the work item /// @@ -143,39 +119,7 @@ public class WorkItemMigrationConfig : IWorkItemProcessorConfig /// [] public IList WorkItemIDs { get; set; } - /// - /// Sets the maximum number of revisions that will be migrated. "First + Last N = Max". - /// If this was set to 5 and there were 10 revisions you would get the first 1 (creation) and the latest 4 migrated. - /// - /// 0 - public int MaxRevisions { get; set; } - - /// - /// - /// - /// ? - public bool UseCommonNodeStructureEnricherConfig { get; set; } - - /// - /// The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) - /// - /// ["/"] - public string[] NodeBasePaths { get; set; } - - /// - /// Remapping rules for area paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, - /// that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - /// - /// {} - public Dictionary AreaMaps { get; set; } - - /// - /// Remapping rules for iteration paths, implemented with regular expressions. The rules apply with a higher priority than the `PrefixProjectToNodes`, - /// that is, if no rule matches the path and the `PrefixProjectToNodes` option is enabled, then the old `PrefixProjectToNodes` behavior is applied. - /// - /// {} - public Dictionary IterationMaps { get; set; } - + /// /// 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. @@ -193,11 +137,6 @@ public class WorkItemMigrationConfig : IWorkItemProcessorConfig /// public bool SkipRevisionWithInvalidAreaPath { get; set; } - /// - /// When set to True the susyem will try to create any missing missing area or iteration paths from the revisions. - /// - public bool ShouldCreateMissingRevisionPaths { get; set; } - /// public bool IsProcessorCompatible(IReadOnlyList otherProcessors) { @@ -212,28 +151,20 @@ public WorkItemMigrationConfig() Enabled = false; WorkItemCreateRetryLimit = 5; FilterWorkItemsThatAlreadyExistInTarget = false; - ReplayRevisions = true; - LinkMigration = true; AttachmentMigration = true; FixHtmlAttachmentLinks = false; AttachmentWorkingPath = "c:\\temp\\WorkItemAttachmentWorkingFolder\\"; AttachmentMaxSize = 480000000; UpdateCreatedBy = true; - PrefixProjectToNodes = false; UpdateCreatedDate = true; SkipToFinalRevisedWorkItemType = false; LinkMigrationSaveEachAsAdded = false; GenerateMigrationComment = true; - WIQLQueryBit = @"AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request')"; - WIQLOrderBit = "[System.ChangedDate] desc"; - MaxRevisions = 0; + 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"; AttachRevisionHistory = false; - AreaMaps = new Dictionary(); - IterationMaps = new Dictionary(); MaxGracefulFailures = 0; SkipRevisionWithInvalidIterationPath = false; SkipRevisionWithInvalidAreaPath = false; - ShouldCreateMissingRevisionPaths = true; } } } \ No newline at end of file diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemPostProcessingConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemPostProcessingConfig.cs index e64391fe7..007082cc2 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemPostProcessingConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemPostProcessingConfig.cs @@ -23,13 +23,8 @@ public string Processor /// 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) /// /// AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - public string WIQLQueryBit { get; set; } + public string WIQLQuery { get; set; } - /// - /// A work item query to affect the order in which the work items are migrated. Don't leave this empty. - /// - /// [System.ChangedDate] desc - public string WIQLOrderBit { get; set; } /// /// 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. @@ -59,7 +54,7 @@ public bool IsProcessorCompatible(IReadOnlyList otherProcessor public WorkItemPostProcessingConfig() { - WIQLQueryBit = "AND [TfsMigrationTool.ReflectedWorkItemId] = '' "; + 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"; } } } \ No newline at end of file diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs index b20330f49..9613c83d1 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/WorkItemUpdateConfig.cs @@ -17,13 +17,8 @@ public string Processor /// 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) /// /// AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') - public string WIQLQueryBit { get; set; } + public string WIQLQuery { get; set; } - /// - /// A work item query to affect the order in which the work items are migrated. Don't leave this empty. - /// - /// [System.ChangedDate] desc - public string WIQLOrderBit { get; set; } /// /// A list of work items to import @@ -59,7 +54,7 @@ public bool IsProcessorCompatible(IReadOnlyList otherProcessor public WorkItemUpdateConfig() { - WIQLQueryBit = @"AND [TfsMigrationTool.ReflectedWorkItemId] = '' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] IN ('Shared Steps', 'Shared Parameter', 'Test Case', 'Requirement', 'Task', 'User Story', 'Bug')"; + 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"; } } } \ No newline at end of file diff --git a/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs b/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs index 565fd1b7c..42fa24841 100644 --- a/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs +++ b/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs @@ -54,21 +54,14 @@ public void Setup() _services.GetRequiredService>()); _underTest.Configure(new WorkItemMigrationConfig { - AreaMaps = new Dictionary - { - {"SourceServer", "TargetServer"} - }, - IterationMaps = new Dictionary - { - {"SourceServer", "TargetServer"} - }, + }); } [TestMethod] public void TestFixAreaPath_WhenNoAreaPathOrIterationPath_DoesntChangeQuery() { - string WIQLQueryBit = @"AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -79,8 +72,8 @@ public void TestFixAreaPath_WhenNoAreaPathOrIterationPath_DoesntChangeQuery() [TestMethod] public void TestFixAreaPath_WhenAreaPathInQuery_ChangesQuery() { - string WIQLQueryBit = @"AND [System.AreaPath] = 'SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND [System.AreaPath] = 'TargetServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'TargetServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -100,12 +93,12 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesEnabled_ PrefixProjectToNodes = true, }); - string WIQLQueryBit = @"AND [System.AreaPath] = 'SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND [System.AreaPath] = 'TargetServer\SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'TargetServer\SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); + string targetWIQLQuery = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); - Assert.AreEqual(expectTargetQueryBit, targetWIQLQueryBit); + Assert.AreEqual(expectTargetQueryBit, targetWIQLQuery); } [TestMethod] @@ -127,8 +120,8 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesDisabled IterationMaps = new Dictionary(), }); - string WIQLQueryBit = @"AND [System.AreaPath] = 'Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND [System.AreaPath] = 'Target Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'Target Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "Source Project", "Target Project", null); @@ -155,8 +148,8 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesEnabled_ PrefixProjectToNodes = true, }); - var WIQLQueryBit = @"AND [System.AreaPath] = 'Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - var expectTargetQueryBit = @"AND [System.AreaPath] = 'Target Project\Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + var WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + var expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'Target Project\Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; var targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "Source Project", "Target Project", null); @@ -166,8 +159,8 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesEnabled_ [TestMethod] public void TestFixAreaPath_WhenMultipleAreaPathInQuery_ChangesQuery() { - string WIQLQueryBit = @"AND [System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] = 'SourceServer\Area\Path2' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND [System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] = 'TargetServer\Area\Path2' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] = 'SourceServer\Area\Path2' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] = 'TargetServer\Area\Path2' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -177,8 +170,8 @@ public void TestFixAreaPath_WhenMultipleAreaPathInQuery_ChangesQuery() [TestMethod] public void TestFixAreaPath_WhenAreaPathAtEndOfQuery_ChangesQuery() { - string WIQLQueryBit = @"AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') AND [System.AreaPath] = 'SourceServer\Area\Path1'"; - string expectTargetQueryBit = @"AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') AND [System.AreaPath] = 'TargetServer\Area\Path1'"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') AND [System.AreaPath] = 'SourceServer\Area\Path1'"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan') AND [System.AreaPath] = 'TargetServer\Area\Path1'"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -188,8 +181,8 @@ public void TestFixAreaPath_WhenAreaPathAtEndOfQuery_ChangesQuery() [TestMethod] public void TestFixIterationPath_WhenInQuery_ChangesQuery() { - string WIQLQueryBit = @"AND [System.IterationPath] = 'SourceServer\Iteration\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND [System.IterationPath] = 'TargetServer\Iteration\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.IterationPath] = 'SourceServer\Iteration\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.IterationPath] = 'TargetServer\Iteration\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -199,8 +192,8 @@ public void TestFixIterationPath_WhenInQuery_ChangesQuery() [TestMethod] public void TestFixAreaPathAndIteration_WhenMultipleOccuranceInQuery_ChangesQuery() { - string WIQLQueryBit = @"AND ([System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] = 'SourceServer\Area\Path2') AND ([System.IterationPath] = 'SourceServer\Iteration\Path1' OR [System.IterationPath] = 'SourceServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND ([System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] = 'TargetServer\Area\Path2') AND ([System.IterationPath] = 'TargetServer\Iteration\Path1' OR [System.IterationPath] = 'TargetServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND ([System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] = 'SourceServer\Area\Path2') AND ([System.IterationPath] = 'SourceServer\Iteration\Path1' OR [System.IterationPath] = 'SourceServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND ([System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] = 'TargetServer\Area\Path2') AND ([System.IterationPath] = 'TargetServer\Iteration\Path1' OR [System.IterationPath] = 'TargetServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); @@ -210,8 +203,8 @@ public void TestFixAreaPathAndIteration_WhenMultipleOccuranceInQuery_ChangesQuer [TestMethod] public void TestFixAreaPathAndIteration_WhenMultipleOccuranceWithMixtureOrEqualAndUnderOperatorsInQuery_ChangesQuery() { - string WIQLQueryBit = @"AND ([System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] UNDER 'SourceServer\Area\Path2') AND ([System.IterationPath] UNDER 'SourceServer\Iteration\Path1' OR [System.IterationPath] = 'SourceServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; - string expectTargetQueryBit = @"AND ([System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] UNDER 'TargetServer\Area\Path2') AND ([System.IterationPath] UNDER 'TargetServer\Iteration\Path1' OR [System.IterationPath] = 'TargetServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND ([System.AreaPath] = 'SourceServer\Area\Path1' OR [System.AreaPath] UNDER 'SourceServer\Area\Path2') AND ([System.IterationPath] UNDER 'SourceServer\Iteration\Path1' OR [System.IterationPath] = 'SourceServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; + string expectTargetQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND ([System.AreaPath] = 'TargetServer\Area\Path1' OR [System.AreaPath] UNDER 'TargetServer\Area\Path2') AND ([System.IterationPath] UNDER 'TargetServer\Iteration\Path1' OR [System.IterationPath] = 'TargetServer\Iteration\Path2') AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; string targetWIQLQueryBit = _underTest.FixAreaPathAndIterationPathForTargetQuery(WIQLQueryBit, "SourceServer", "TargetServer", null); diff --git a/src/VstsSyncMigrator.Core/Execution/ComponentContext/TestManagementContext.cs b/src/VstsSyncMigrator.Core/Execution/ComponentContext/TestManagementContext.cs index 61522f668..10f16d226 100644 --- a/src/VstsSyncMigrator.Core/Execution/ComponentContext/TestManagementContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/ComponentContext/TestManagementContext.cs @@ -8,7 +8,7 @@ namespace VstsSyncMigrator.Engine.ComponentContext { public class TestManagementContext { - private readonly string testPlanQueryBit; + private readonly string testPlanQuery; private readonly IMigrationClient _source; private ITestManagementService tms; @@ -18,9 +18,9 @@ public TestManagementContext(IMigrationClient source) : this(source, null) { } - public TestManagementContext(IMigrationClient source, string testPlanQueryBit) + public TestManagementContext(IMigrationClient source, string testPlanQuery) { - this.testPlanQueryBit = testPlanQueryBit; + this.testPlanQuery = testPlanQuery; _source = source; tms = _source.GetService(); Project = tms.GetTeamProject(source.Config.AsTeamProjectConfig().Project); @@ -28,9 +28,9 @@ public TestManagementContext(IMigrationClient source, string testPlanQueryBit) internal List GetTestPlans() { - var query = (string.IsNullOrWhiteSpace(testPlanQueryBit)) + var query = (string.IsNullOrWhiteSpace(testPlanQuery)) ? "Select * From TestPlan" - : $"Select * From TestPlan Where {testPlanQueryBit}"; + : $"Select * From TestPlan Where {testPlanQuery}"; var testPlans = Project.TestPlans.Query(query); diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs index 0115df1be..4e3939a5e 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs @@ -75,29 +75,17 @@ public override void Configure(IProcessorConfig config) { _config = (TestPlansAndSuitesMigrationConfig)config; - if (_config.UseCommonNodeStructureEnricherConfig) - { + var nodeStructureOptions = _engineConfig.CommonEnrichersConfig.OfType().FirstOrDefault() ?? throw new InvalidOperationException("Cannot use common node structure because it is not found."); _nodeStructureEnricher.Configure(nodeStructureOptions); - } - else - { - _nodeStructureEnricher.Configure(new TfsNodeStructureOptions() - { - Enabled = true, - NodeBasePaths = _config.NodeBasePaths, - PrefixProjectToNodes = _config.PrefixProjectToNodes, - AreaMaps = _config.AreaMaps ?? new Dictionary(), - IterationMaps = _config.IterationMaps ?? new Dictionary(), - }); - } + } protected override void InternalExecute() { - _sourceTestStore = new TestManagementContext(Engine.Source, _config.TestPlanQueryBit); + _sourceTestStore = new TestManagementContext(Engine.Source, _config.TestPlanQuery); _targetTestStore = new TestManagementContext(Engine.Target); _sourceTestConfigs = _sourceTestStore.Project.TestConfigurations.Query("Select * From TestConfiguration"); _targetTestConfigs = _targetTestStore.Project.TestConfigurations.Query("Select * From TestConfiguration"); @@ -709,7 +697,8 @@ private void FixQueryForTeamProjectNameChange(ITestSuiteBase source, IDynamicTes Log.LogInformation("Team Project names dont match. We need to fix the query in dynamic test suite {0} - {1}.", source.Id, source.Title); Log.LogInformation("Replacing old project name {1} in query {0} with new team project name {2}", targetSuiteChild.Query.QueryText, source.Plan.Project.TeamProjectName, targetTestStore.Project.TeamProjectName); // First need to check is prefix project nodes has been applied for the migration - if (_config.PrefixProjectToNodes) + + if (_nodeStructureEnricher.Options.PrefixProjectToNodes) { // if prefix project nodes has been applied we need to take the original area/iteration value and prefix targetSuiteChild.Query = @@ -875,7 +864,7 @@ private void ProcessTestPlan(ITestPlan sourcePlan) var parameters = new Dictionary(); AddParameter("PlanId", parameters, sourcePlan.Id.ToString()); //////////////////////////////////// - var newPlanName = _config.PrefixProjectToNodes + var newPlanName = _nodeStructureEnricher.Options.PrefixProjectToNodes ? $"{Engine.Source.WorkItems.GetProject().Name}-{sourcePlan.Name}" : $"{sourcePlan.Name}"; InnerLog(sourcePlan, $"Process Plan {newPlanName}", 0, true); diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs index 145c4bc99..9f494658b 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs @@ -101,30 +101,50 @@ public override void Configure(IProcessorConfig config) { _config = (WorkItemMigrationConfig)config; - if (_config.UseCommonNodeStructureEnricherConfig) + ImportCommonEnricherConfigs(); + } + + private void ImportCommonEnricherConfigs() + { + /// setup _engineConfig.CommonEnrichersConfig + if (_engineConfig.CommonEnrichersConfig == null) + { + Log.LogError("CommonEnrichersConfig cant be Null! it must be a minimum of `[]`"); + Environment.Exit(-1); + } + // TfsNodeStructureOptions + var nseConfig = _engineConfig.CommonEnrichersConfig.OfType().FirstOrDefault(); + if (nseConfig == null) + { + _nodeStructureEnricher.Configure(TfsNodeStructureOptions.GetDefaults()); + Log.LogWarning("Default `TfsNodeStructureOptions` used... add a `TfsNodeStructureOptions` entry to `CommonEnrichersConfig` to customise the settings."); + } + else { - var nseConfig = _engineConfig.CommonEnrichersConfig.OfType() - .FirstOrDefault() ?? - throw new InvalidOperationException( - "Cannot use common node structure because it is not found."); _nodeStructureEnricher.Configure(nseConfig); } + // TfsNodeStructureOptions + var revmanConfig = _engineConfig.CommonEnrichersConfig.OfType().FirstOrDefault(); + if (revmanConfig == null) + { + _revisionManager.Configure(TfsRevisionManagerOptions.GetDefaults()); + Log.LogWarning("Default `TfsRevisionManagerOptions` used... add a `TfsRevisionManagerOptions` entry to `CommonEnrichersConfig` to customise the settings."); + } else { - _nodeStructureEnricher.Configure(new TfsNodeStructureOptions - { - Enabled = true, - NodeBasePaths = _config.NodeBasePaths, - PrefixProjectToNodes = _config.PrefixProjectToNodes, - AreaMaps = _config.AreaMaps ?? new Dictionary(), - IterationMaps = _config.IterationMaps ?? new Dictionary(), - ShouldCreateMissingRevisionPaths = _config.ShouldCreateMissingRevisionPaths - }); ; + _revisionManager.Configure(revmanConfig); + } + // TfsNodeStructureOptions + var wileConfig = _engineConfig.CommonEnrichersConfig.OfType().FirstOrDefault(); + if (revmanConfig == null) + { + _workItemLinkEnricher.Configure(TfsWorkItemLinkEnricherOptions.GetDefaults()); + Log.LogWarning("Default `TfsWorkItemLinkEnricherOptions` used... add a `TfsWorkItemLinkEnricherOptions` entry to `CommonEnrichersConfig` to customise the settings."); + } + else + { + _workItemLinkEnricher.Configure(wileConfig); } - - _revisionManager.Configure(new TfsRevisionManagerOptions() { Enabled = true, MaxRevisions = _config.MaxRevisions, ReplayRevisions = _config.ReplayRevisions }); - - _workItemLinkEnricher.Configure(_config.LinkMigrationSaveEachAsAdded, _config.FilterWorkItemsThatAlreadyExistInTarget); } internal void TraceWriteLine(LogEventLevel level, string message, Dictionary properties = null) @@ -166,14 +186,9 @@ protected override void InternalExecute() PopulateIgnoreList(); - string sourceQuery = - string.Format( - @"SELECT [System.Id], [System.Tags] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {0} ORDER BY {1}", - _config.WIQLQueryBit, _config.WIQLOrderBit); - // Inform the user that he maybe has to be patient now - contextLog.Information("Querying items to be migrated: {SourceQuery} ...", sourceQuery); - var sourceWorkItems = Engine.Source.WorkItems.GetWorkItems(sourceQuery); + contextLog.Information("Querying items to be migrated: {SourceQuery} ...", _config.WIQLQuery); + var sourceWorkItems = Engine.Source.WorkItems.GetWorkItems(_config.WIQLQuery); contextLog.Information("Replay all revisions of {sourceWorkItemsCount} work items?", sourceWorkItems.Count); @@ -214,11 +229,11 @@ protected override void InternalExecute() "[FilterWorkItemsThatAlreadyExistInTarget] is enabled. Searching for work items that have already been migrated to the target...", sourceWorkItems.Count()); - string targetWIQLQueryBit = FixAreaPathAndIterationPathForTargetQuery(_config.WIQLQueryBit, + string targetWIQLQuery = FixAreaPathAndIterationPathForTargetQuery(_config.WIQLQuery, Engine.Source.WorkItems.Project.Name, Engine.Target.WorkItems.Project.Name, contextLog); + sourceWorkItems = ((TfsWorkItemMigrationClient)Engine.Target.WorkItems).FilterExistingWorkItems( - sourceWorkItems, - new TfsWiqlDefinition() { OrderBit = _config.WIQLOrderBit, QueryBit = targetWIQLQueryBit }, + sourceWorkItems, _config.WIQLQuery, (TfsWorkItemMigrationClient)Engine.Source.WorkItems); contextLog.Information( "!! After removing all found work items there are {SourceWorkItemCount} remaining to be migrated.", @@ -348,24 +363,24 @@ private void ValidatePatTokenRequirement() } } - internal string FixAreaPathAndIterationPathForTargetQuery(string sourceWIQLQueryBit, string sourceProject, string targetProject, ILogger? contextLog) + internal string FixAreaPathAndIterationPathForTargetQuery(string sourceWIQLQuery, string sourceProject, string targetProject, ILogger? contextLog) { - string targetWIQLQueryBit = sourceWIQLQueryBit; + string targetWIQLQuery = sourceWIQLQuery; - if (string.IsNullOrWhiteSpace(targetWIQLQueryBit)) + if (string.IsNullOrWhiteSpace(targetWIQLQuery)) { - return targetWIQLQueryBit; + return targetWIQLQuery; } - var matches = Regex.Matches(targetWIQLQueryBit, RegexPatternForAreaAndIterationPathsFix); + var matches = Regex.Matches(targetWIQLQuery, RegexPatternForAreaAndIterationPathsFix); if (string.IsNullOrWhiteSpace(sourceProject) || string.IsNullOrWhiteSpace(targetProject) || sourceProject == targetProject) { - return targetWIQLQueryBit; + return targetWIQLQuery; } foreach (Match match in matches) @@ -389,12 +404,12 @@ internal string FixAreaPathAndIterationPathForTargetQuery(string sourceWIQLQuery } var remappedPath = _nodeStructureEnricher.GetNewNodeName(value, structureType); - targetWIQLQueryBit = targetWIQLQueryBit.Replace(value, remappedPath); + targetWIQLQuery = targetWIQLQuery.Replace(value, remappedPath); } - contextLog?.Information("[FilterWorkItemsThatAlreadyExistInTarget] is enabled. Source project {sourceProject} is replaced with target project {targetProject} on the WIQLQueryBit which resulted into this target WIQLQueryBit \"{targetWIQLQueryBit}\" .", sourceProject, targetProject, targetWIQLQueryBit); + contextLog?.Information("[FilterWorkItemsThatAlreadyExistInTarget] is enabled. Source project {sourceProject} is replaced with target project {targetProject} on the WIQLQueryBit which resulted into this target WIQLQueryBit \"{targetWIQLQueryBit}\" .", sourceProject, targetProject, targetWIQLQuery); - return targetWIQLQueryBit; + return targetWIQLQuery; } private static bool IsNumeric(string val, NumberStyles numberStyle) @@ -565,7 +580,7 @@ private async Task ProcessWorkItemAsync(WorkItemData sourceWorkItem, int retryLi TraceWriteLine(LogEventLevel.Information, "Work Item has {sourceWorkItemRev} revisions and revision migration is set to {ReplayRevisions}", new Dictionary(){ { "sourceWorkItemRev", sourceWorkItem.Rev }, - { "ReplayRevisions", _config.ReplayRevisions }} + { "ReplayRevisions", _revisionManager.Options.ReplayRevisions }} ); List revisionsToMigrate = _revisionManager.GetRevisionsToMigrate(sourceWorkItem, targetWorkItem); if (targetWorkItem == null) @@ -681,9 +696,9 @@ private void ProcessWorkItemAttachments(WorkItemData sourceWorkItem, WorkItemDat private void ProcessWorkItemLinks(IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, WorkItemData sourceWorkItem, WorkItemData targetWorkItem) { - if (targetWorkItem != null && _config.LinkMigration && sourceWorkItem.ToWorkItem().Links.Count > 0) + if (targetWorkItem != null && _workItemLinkEnricher.Options.Enabled && sourceWorkItem.ToWorkItem().Links.Count > 0) { - TraceWriteLine(LogEventLevel.Information, "Links {SourceWorkItemLinkCount} | LinkMigrator:{LinkMigration}", new Dictionary() { { "SourceWorkItemLinkCount", sourceWorkItem.ToWorkItem().Links.Count }, { "LinkMigration", _config.LinkMigration } }); + TraceWriteLine(LogEventLevel.Information, "Links {SourceWorkItemLinkCount} | LinkMigrator:{LinkMigration}", new Dictionary() { { "SourceWorkItemLinkCount", sourceWorkItem.ToWorkItem().Links.Count }, { "LinkMigration", _workItemLinkEnricher.Options.Enabled } }); _workItemLinkEnricher.Enrich(sourceWorkItem, targetWorkItem); AddMetric("RelatedLinkCount", processWorkItemMetrics, targetWorkItem.ToWorkItem().Links.Count); int fixedLinkCount = gitRepositoryEnricher.Enrich(sourceWorkItem, targetWorkItem); diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemPostProcessingContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemPostProcessingContext.cs index 527de3e1b..84d8bf3e9 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemPostProcessingContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemPostProcessingContext.cs @@ -49,8 +49,7 @@ protected override void InternalExecute() var wiqbFactory = Services.GetRequiredService(); var wiqb = wiqbFactory.Create(); //Builds the constraint part of the query - string constraints = BuildQueryBitConstraints(); - wiqb.Query = string.Format(@"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {0} ORDER BY [System.Id] ", constraints); + wiqb.Query = _config.WIQLQuery; List sourceWIS = Engine.Target.WorkItems.GetWorkItems(wiqb); Log.LogInformation("Migrate {0} work items?", sourceWIS.Count); @@ -107,39 +106,5 @@ protected override void InternalExecute() Log.LogInformation("DONE in {Elapsed}", stopwatch.Elapsed.ToString("c")); } - private string BuildQueryBitConstraints() - { - string constraints = ""; - - if (_config.WorkItemIDs != null && _config.WorkItemIDs.Count > 0) - { - if (_config.WorkItemIDs.Count == 1) - { - constraints += string.Format(" AND [System.Id] = {0} ", _config.WorkItemIDs[0]); - } - else - { - constraints += string.Format(" AND [System.Id] IN ({0}) ", string.Join(",", _config.WorkItemIDs)); - } - } - - if (Engine.TypeDefinitionMaps.Items != null && Engine.TypeDefinitionMaps.Items.Count > 0) - { - if (Engine.TypeDefinitionMaps.Items.Count == 1) - { - constraints += string.Format(" AND [System.WorkItemType] = '{0}' ", Engine.TypeDefinitionMaps.Items.Keys.First()); - } - else - { - constraints += string.Format(" AND [System.WorkItemType] IN ('{0}') ", string.Join("','", Engine.TypeDefinitionMaps.Items.Keys)); - } - } - - if (!string.IsNullOrEmpty(_config.WIQLQueryBit)) - { - constraints += _config.WIQLQueryBit; - } - return constraints; - } } } \ No newline at end of file diff --git a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/FixGitCommitLinks.cs b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/FixGitCommitLinks.cs index 4d9e4975e..8e3730d3c 100644 --- a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/FixGitCommitLinks.cs +++ b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/FixGitCommitLinks.cs @@ -42,13 +42,7 @@ protected override void InternalExecute() { Stopwatch stopwatch = Stopwatch.StartNew(); ////////////////////////////////////////////////// - var query = - string.Format( - @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {0} ORDER BY {1}", - _config.QueryBit, - _config.OrderBit - ); - List workitems = Engine.Target.WorkItems.GetWorkItems(query); + List workitems = Engine.Target.WorkItems.GetWorkItems(_config.Query); Log.LogInformation("Update {0} work items?", workitems.Count); ///////////////////////////////////////////////// int current = workitems.Count; diff --git a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemDelete.cs b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemDelete.cs index 1b4b0fa6e..e2ae0793f 100644 --- a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemDelete.cs +++ b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemDelete.cs @@ -44,11 +44,7 @@ protected override void InternalExecute() { Stopwatch stopwatch = Stopwatch.StartNew(); ////////////////////////////////////////////////// - string sourceQuery = - string.Format( - @"SELECT [System.Id], [System.Tags] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {0} ORDER BY {1}", - _config.WIQLQueryBit, _config.WIQLOrderBit); - var workItems = Engine.Target.WorkItems.GetWorkItemIds(sourceQuery); + var workItems = Engine.Target.WorkItems.GetWorkItemIds(_config.WIQLQuery); if (workItems.Count > 0) { diff --git a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs index ae1f52d33..13cbd72b5 100644 --- a/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs +++ b/src/VstsSyncMigrator.Core/Execution/ProcessingContext/WorkItemUpdate.cs @@ -39,8 +39,7 @@ protected override void InternalExecute() { Stopwatch stopwatch = Stopwatch.StartNew(); ////////////////////////////////////////////////// - var Query = string.Format(@"SELECT [System.Id], [System.Tags] FROM WorkItems WHERE [System.TeamProject] = @TeamProject {0} ORDER BY [System.ChangedDate] desc", _config.WIQLQueryBit); - List workitems = Engine.Target.WorkItems.GetWorkItems(Query); + List workitems = Engine.Target.WorkItems.GetWorkItems(_config.WIQLQuery); Log.LogInformation("Update {0} work items?", workitems.Count); ////////////////////////////////////////////////// int current = workitems.Count; From 6d7c6402a10b92f86d96af9e085a350ca2e0e884 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Tue, 30 Jan 2024 14:10:04 +0000 Subject: [PATCH 2/6] Preview Trying to resolve Wacatac.B!ml (#1888) --- docs/Reference/Generated/MigrationTools.xml | 6 ------ ...rationTools.Clients.AzureDevops.ObjectModel.Tests.csproj | 4 ++-- .../MigrationTools.Clients.AzureDevops.Rest.Tests.csproj | 4 ++-- .../MigrationTools.Clients.FileSystem.Tests.csproj | 4 ++-- .../MigrationTools.Clients.InMemory.Tests.csproj | 4 ++-- .../MigrationTools.ConsoleDataGenerator.csproj | 2 +- .../MigrationTools.Host.Tests.csproj | 4 ++-- .../MigrationTools.Integration.Tests.csproj | 4 ++-- src/MigrationTools.Tests/MigrationTools.Tests.csproj | 4 ++-- .../VstsSyncMigrator.Core.Tests.csproj | 4 ++-- src/VstsSyncMigrator.Core/VstsSyncMigrator.Core.csproj | 2 +- 11 files changed, 18 insertions(+), 24 deletions(-) diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 483f29a40..6de2451f3 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -368,12 +368,6 @@ ? - - - If enabled this will migrate the Links for the work item at the same time as the whole work item. - - true - If enabled this will migrate all of the attachments at the same time as the work item diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj index 7867bbb4d..f3db041f9 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel.Tests/MigrationTools.Clients.AzureDevops.ObjectModel.Tests.csproj @@ -13,8 +13,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj index a016b3a16..9e157d3e0 100644 --- a/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj +++ b/src/MigrationTools.Clients.AzureDevops.Rest.Tests/MigrationTools.Clients.AzureDevops.Rest.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj b/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj index 172bdc9fd..983785e3c 100644 --- a/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj +++ b/src/MigrationTools.Clients.FileSystem.Tests/MigrationTools.Clients.FileSystem.Tests.csproj @@ -12,8 +12,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj b/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj index ee7728c33..1b4619967 100644 --- a/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj +++ b/src/MigrationTools.Clients.InMemory.Tests/MigrationTools.Clients.InMemory.Tests.csproj @@ -12,8 +12,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.ConsoleDataGenerator/MigrationTools.ConsoleDataGenerator.csproj b/src/MigrationTools.ConsoleDataGenerator/MigrationTools.ConsoleDataGenerator.csproj index 8846eb27d..d9e46dbc8 100644 --- a/src/MigrationTools.ConsoleDataGenerator/MigrationTools.ConsoleDataGenerator.csproj +++ b/src/MigrationTools.ConsoleDataGenerator/MigrationTools.ConsoleDataGenerator.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj b/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj index 9ce78801f..be4826dc5 100644 --- a/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj +++ b/src/MigrationTools.Host.Tests/MigrationTools.Host.Tests.csproj @@ -17,8 +17,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.Integration.Tests/MigrationTools.Integration.Tests.csproj b/src/MigrationTools.Integration.Tests/MigrationTools.Integration.Tests.csproj index 16f7d8c60..da36aa230 100644 --- a/src/MigrationTools.Integration.Tests/MigrationTools.Integration.Tests.csproj +++ b/src/MigrationTools.Integration.Tests/MigrationTools.Integration.Tests.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MigrationTools.Tests/MigrationTools.Tests.csproj b/src/MigrationTools.Tests/MigrationTools.Tests.csproj index 488f841dd..d7807fe6b 100644 --- a/src/MigrationTools.Tests/MigrationTools.Tests.csproj +++ b/src/MigrationTools.Tests/MigrationTools.Tests.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/VstsSyncMigrator.Core.Tests/VstsSyncMigrator.Core.Tests.csproj b/src/VstsSyncMigrator.Core.Tests/VstsSyncMigrator.Core.Tests.csproj index 6d0e2ccbd..6f851f3e0 100644 --- a/src/VstsSyncMigrator.Core.Tests/VstsSyncMigrator.Core.Tests.csproj +++ b/src/VstsSyncMigrator.Core.Tests/VstsSyncMigrator.Core.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/VstsSyncMigrator.Core/VstsSyncMigrator.Core.csproj b/src/VstsSyncMigrator.Core/VstsSyncMigrator.Core.csproj index 2a609b203..e69f06e6f 100644 --- a/src/VstsSyncMigrator.Core/VstsSyncMigrator.Core.csproj +++ b/src/VstsSyncMigrator.Core/VstsSyncMigrator.Core.csproj @@ -22,7 +22,7 @@ - + all none From 765c8d423f363e1d2f8d05adb9cd549abfaecae1 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Tue, 30 Jan 2024 20:00:13 +0000 Subject: [PATCH 3/6] Update choco files (#1890) --- ...y.AzureDevOpsMigrationTools-Preview.nuspec | 25 +++++++++++++++++++ ...dAgility.AzureDevOpsMigrationTools.nuspec} | 0 2 files changed, 25 insertions(+) create mode 100644 src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools-Preview.nuspec rename src/MigrationTools.Chocolatey/{vstssyncmigration.nuspec => nkdAgility.AzureDevOpsMigrationTools.nuspec} (100%) diff --git a/src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools-Preview.nuspec b/src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools-Preview.nuspec new file mode 100644 index 000000000..959ce55b2 --- /dev/null +++ b/src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools-Preview.nuspec @@ -0,0 +1,25 @@ + + + + nkdAgility.AzureDevOpsMigrationTools.Preview + Azure DevOps Migration Tools (Preview) + 0.0.0 + MrHinsh + nkdAgility, MrHinsh + Azure DevOps Migration Tools allows migration of Work Item and Test data between Projects in Azure DevOps and TFS. + Azure Devops Migration Tools allow you to bulk edit data in Microsoft Team Foundation Server (TFS) and Azure DevOps Services. Supports both migration and bulk update scenarios. + https://github.com/nkdAgility/azure-devops-migration-tools/ + https://chocolatey.org/packages/vsts-sync-migrator/ + https://nkdagility.com/docs/azure-devops-migration-tools/ + https://github.com/nkdAgility/azure-devops-migration-tools/issues + WIT WorkItem AzureDevOps TFS VSTS VSO VisualStudioOnline DevOps Microsoft TestManagement + https://nkdagility.com/docs/azure-devops-migration-tools/ + https://github.com/nkdAgility/azure-devops-migration-tools/blob/master/LICENSE + false + http://nkdagility.com/wp-content/uploads/2014/03/cropped-nakedalm-logo-260.png + + + + + + \ No newline at end of file diff --git a/src/MigrationTools.Chocolatey/vstssyncmigration.nuspec b/src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools.nuspec similarity index 100% rename from src/MigrationTools.Chocolatey/vstssyncmigration.nuspec rename to src/MigrationTools.Chocolatey/nkdAgility.AzureDevOpsMigrationTools.nuspec From cb654f08f88c27964be5522c14f726a43678556e Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Mon, 5 Feb 2024 16:52:06 +0000 Subject: [PATCH 4/6] [Preview] remove prefix project to nodes (#1902) --- configuration.json | 1 - configuration2-wit.json | 1 - configuration2.json | 2 -- ...onTools.Clients.AzureDevops.ObjectModel.xml | 12 ------------ docs/Reference/Generated/MigrationTools.xml | 6 ------ .../WorkItemMigrationContext-notes.md | 9 +++++++-- .../TfsNodeStructure-notes.md | 15 +++++++++++++++ ...nce.v1.processors.teammigrationcontext.yaml | 5 ----- ...v1.processors.workitemmigrationcontext.yaml | 16 +++------------- ...v2.processorenrichers.tfsnodestructure.yaml | 5 ----- ...rocessors.tfsareaanditerationprocessor.yaml | 5 ----- ...2.processors.workitemtrackingprocessor.yaml | 5 ----- ...rence.v1.processors.teammigrationcontext.md | 5 ----- ...e.v1.processors.workitemmigrationcontext.md | 16 +++------------- ...e.v2.processorenrichers.tfsnodestructure.md | 5 ----- ....processors.tfsareaanditerationprocessor.md | 5 ----- ....v2.processors.workitemtrackingprocessor.md | 5 ----- .../ProcessorEnrichers/TfsNodeStructure.cs | 4 +--- .../TfsNodeStructureOptions.cs | 8 -------- .../TfsWorkItemLinkEnricherOptions.cs | 4 ++-- .../Processors/TfsAreaAndIterationProcessor.cs | 1 - .../TfsAreaAndIterationProcessorOptions.cs | 6 ------ .../AzureDevOpsObjectModelTests.cs | 1 - src/MigrationTools.Samples/configuration.json | 1 - .../WorkItemMigrationProcessorTests.cs | 2 -- .../WorkItemTrackingProcessorOptions.cs | 2 -- .../EngineConfigurationBuilder.cs | 1 - .../Processing/TeamMigrationConfig.cs | 6 ------ .../WorkItemMigrationTests.cs | 2 -- .../TestPlansAndSuitesMigrationContext.cs | 18 +++--------------- .../WorkItemMigrationContext.cs | 10 +++++++--- 31 files changed, 41 insertions(+), 143 deletions(-) diff --git a/configuration.json b/configuration.json index 5ac9297a6..561df5b73 100644 --- a/configuration.json +++ b/configuration.json @@ -34,7 +34,6 @@ "CommonEnrichersConfig": [ { "$type": "TfsNodeStructureOptions", - "PrefixProjectToNodes": false, "NodeBasePaths": [], "AreaMaps": { "^Skypoint Cloud$": "MigrationTest5" diff --git a/configuration2-wit.json b/configuration2-wit.json index 594803ec5..c7fc4bb56 100644 --- a/configuration2-wit.json +++ b/configuration2-wit.json @@ -69,7 +69,6 @@ "$type": "WorkItemTrackingProcessorOptions", "Enabled": false, "ReplayRevisions": true, - "PrefixProjectToNodes": false, "CollapseRevisions": false, "WorkItemCreateRetryLimit": 5, "Enrichers": [ diff --git a/configuration2.json b/configuration2.json index f3381528c..bcc42c749 100644 --- a/configuration2.json +++ b/configuration2.json @@ -177,7 +177,6 @@ "$type": "WorkItemTrackingProcessorOptions", "Enabled": false, "ReplayRevisions": true, - "PrefixProjectToNodes": false, "CollapseRevisions": false, "WorkItemCreateRetryLimit": 5, "Enrichers": [ @@ -263,7 +262,6 @@ { "$type": "TfsAreaAndIterationProcessorOptions", "Enabled": true, - "PrefixProjectToNodes": false, "SourceName": "Source", "TargetName": "Target" } diff --git a/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml b/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml index 228550996..3123375f4 100644 --- a/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml +++ b/docs/Reference/Generated/MigrationTools.Clients.AzureDevops.ObjectModel.xml @@ -33,12 +33,6 @@ A boolean indicating whether the path is a parent of any positively selected base path. - - - Prefix the nodes with the new project name. - - false - The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) @@ -100,12 +94,6 @@ Beta Work Items - - - Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - - false - The TfsSharedQueryProcessor enabled you to migrate queries from one locatio nto another. diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml index 6de2451f3..46b4f1947 100644 --- a/docs/Reference/Generated/MigrationTools.xml +++ b/docs/Reference/Generated/MigrationTools.xml @@ -251,12 +251,6 @@ - - - Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - - false - Migrate original team settings after their creation on target team project diff --git a/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md b/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md index af461fb76..ad010b404 100644 --- a/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md +++ b/docs/Reference/v1/Processors/WorkItemMigrationContext-notes.md @@ -1,4 +1,3 @@ - ## WIQL Query Bits The Work Item queries are all built using Work Item [Query Language (WIQL)](https://docs.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax). @@ -48,6 +47,8 @@ Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnric Moved to the ProcessorEnricher [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) + + ## More Complex Team Migrations The above options allow you to bring over a sub-set of the WIs (using the `WIQLQueryBit`) and move their area or iteration path to a default location. However you may wish to do something more complex e.g. re-map the team structure. This can be done with addition of a `FieldMaps` block to configuration in addition to the `NodeBasePaths`. @@ -73,4 +74,8 @@ A complete list of [FieldMaps](../Reference/v1/FieldMaps/index.md) are available ``` -> Note: This mappings could also be achieved with other forms of Field mapper e.g. `RegexFieldMapConfig`, but the value mapper as an example is easy to understand \ No newline at end of file +> Note: This mappings could also be achieved with other forms of Field mapper e.g. `RegexFieldMapConfig`, but the value mapper as an example is easy to understand + +# Removed Properties + +- PrefixProjectToNodes - This option was removed in favour of the Area and Iteration Maps on [TfsNodeStructure](../Reference/v2/ProcessorEnrichers/TfsNodeStructure/) \ No newline at end of file diff --git a/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md b/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md index d445f930d..712f20ff0 100644 --- a/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md +++ b/docs/Reference/v2/ProcessorEnrichers/TfsNodeStructure-notes.md @@ -185,6 +185,21 @@ _NOTE: You need `\\` to escape a `\` the pattern, and `\\` to escape a `\` in JS ![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) +### Example with PrefixProjectToNodes + +This will prepend a bucket to the area and iteration paths. This is useful when you want to keep the original paths but also want to be able to identify them as being from the original project. + +```json + +```json +"AreaMaps": { + "^OriginalProject(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\BucketForIncommingAreas\$2", +}, +"IterationMaps": { + "^OriginalProject(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\BucketForIncommingInterations\$2", +} +``` + ### Example with AreaMaps and IterationMaps ``` diff --git a/docs/_data/reference.v1.processors.teammigrationcontext.yaml b/docs/_data/reference.v1.processors.teammigrationcontext.yaml index 100c31710..c807d43ef 100644 --- a/docs/_data/reference.v1.processors.teammigrationcontext.yaml +++ b/docs/_data/reference.v1.processors.teammigrationcontext.yaml @@ -7,7 +7,6 @@ configurationSamples: { "$type": "TeamMigrationConfig", "Enabled": false, - "PrefixProjectToNodes": false, "EnableTeamSettingsMigration": true, "FixTeamSettingsForExistingTeams": false } @@ -29,10 +28,6 @@ options: type: Boolean description: Reset the target team settings to match the source if the team exists defaultValue: true -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false status: preview processingTarget: Teams classFile: /src/VstsSyncMigrator.Core/Execution/MigrationContext/TeamMigrationContext.cs diff --git a/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml b/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml index aae0c9593..2697ce57b 100644 --- a/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml +++ b/docs/_data/reference.v1.processors.workitemmigrationcontext.yaml @@ -9,9 +9,7 @@ configurationSamples: "Enabled": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, - "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request')", - "WIQLOrderBit": "[System.ChangedDate] desc", - "LinkMigration": 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", "AttachmentMigration": true, "AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\", "FixHtmlAttachmentLinks": false, @@ -66,10 +64,6 @@ options: type: Boolean description: If enabled, adds a comment recording the migration defaultValue: false -- parameterName: LinkMigration - type: Boolean - description: If enabled this will migrate the Links for the work item at the same time as the whole work item. - defaultValue: true - parameterName: LinkMigrationSaveEachAsAdded type: Boolean description: "If you have changed parents before re-running a sync you may get a `TF26194: unable to change the value of the 'Parent' field` error. This will resolve it, but will slow migration." @@ -102,14 +96,10 @@ options: 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: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') + 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.' diff --git a/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml b/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml index a8c5ad26b..478d75f4f 100644 --- a/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml +++ b/docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml @@ -7,7 +7,6 @@ configurationSamples: { "$type": "TfsNodeStructureOptions", "Enabled": true, - "PrefixProjectToNodes": false, "NodeBasePaths": null, "AreaMaps": { "$type": "Dictionary`2" @@ -40,10 +39,6 @@ options: type: String[] description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) defaultValue: '["/"]' -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix the nodes with the new project name. - defaultValue: false - parameterName: RefName type: String description: For internal use diff --git a/docs/_data/reference.v2.processors.tfsareaanditerationprocessor.yaml b/docs/_data/reference.v2.processors.tfsareaanditerationprocessor.yaml index e53074d4d..84159fe11 100644 --- a/docs/_data/reference.v2.processors.tfsareaanditerationprocessor.yaml +++ b/docs/_data/reference.v2.processors.tfsareaanditerationprocessor.yaml @@ -7,7 +7,6 @@ configurationSamples: { "$type": "TfsAreaAndIterationProcessorOptions", "Enabled": false, - "PrefixProjectToNodes": false, "NodeBasePaths": null, "AreaMaps": { "$type": "Dictionary`2" @@ -41,10 +40,6 @@ options: type: String[] description: missng XML code comments defaultValue: missng XML code comments -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false - parameterName: ProcessorEnrichers 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. diff --git a/docs/_data/reference.v2.processors.workitemtrackingprocessor.yaml b/docs/_data/reference.v2.processors.workitemtrackingprocessor.yaml index 9f9c9810b..bb29cf255 100644 --- a/docs/_data/reference.v2.processors.workitemtrackingprocessor.yaml +++ b/docs/_data/reference.v2.processors.workitemtrackingprocessor.yaml @@ -8,7 +8,6 @@ configurationSamples: "$type": "WorkItemTrackingProcessorOptions", "Enabled": true, "ReplayRevisions": true, - "PrefixProjectToNodes": false, "CollapseRevisions": false, "WorkItemCreateRetryLimit": 5, "ProcessorEnrichers": [ @@ -38,10 +37,6 @@ options: 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: PrefixProjectToNodes - type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: ProcessorEnrichers 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. diff --git a/docs/collections/_reference/reference.v1.processors.teammigrationcontext.md b/docs/collections/_reference/reference.v1.processors.teammigrationcontext.md index 997d07940..4953a5ff0 100644 --- a/docs/collections/_reference/reference.v1.processors.teammigrationcontext.md +++ b/docs/collections/_reference/reference.v1.processors.teammigrationcontext.md @@ -8,7 +8,6 @@ configurationSamples: { "$type": "TeamMigrationConfig", "Enabled": false, - "PrefixProjectToNodes": false, "EnableTeamSettingsMigration": true, "FixTeamSettingsForExistingTeams": false } @@ -30,10 +29,6 @@ options: type: Boolean description: Reset the target team settings to match the source if the team exists defaultValue: true -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false status: preview processingTarget: Teams classFile: /src/VstsSyncMigrator.Core/Execution/MigrationContext/TeamMigrationContext.cs diff --git a/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md b/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md index 2bdf53394..b351d0b87 100644 --- a/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md +++ b/docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md @@ -10,9 +10,7 @@ configurationSamples: "Enabled": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, - "WIQLQueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request')", - "WIQLOrderBit": "[System.ChangedDate] desc", - "LinkMigration": 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", "AttachmentMigration": true, "AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\", "FixHtmlAttachmentLinks": false, @@ -67,10 +65,6 @@ options: type: Boolean description: If enabled, adds a comment recording the migration defaultValue: false -- parameterName: LinkMigration - type: Boolean - description: If enabled this will migrate the Links for the work item at the same time as the whole work item. - defaultValue: true - parameterName: LinkMigrationSaveEachAsAdded type: Boolean description: "If you have changed parents before re-running a sync you may get a `TF26194: unable to change the value of the 'Parent' field` error. This will resolve it, but will slow migration." @@ -103,14 +97,10 @@ options: 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: WIQLOrderBit - type: String - description: A work item query to affect the order in which the work items are migrated. Don't leave this empty. - defaultValue: '[System.ChangedDate] desc' -- parameterName: WIQLQueryBit +- 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: AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan','Shared Steps','Shared Parameter','Feedback Request') + 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.' diff --git a/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md b/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md index 8b5175028..dbf79235c 100644 --- a/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md +++ b/docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md @@ -8,7 +8,6 @@ configurationSamples: { "$type": "TfsNodeStructureOptions", "Enabled": true, - "PrefixProjectToNodes": false, "NodeBasePaths": null, "AreaMaps": { "$type": "Dictionary`2" @@ -41,10 +40,6 @@ options: type: String[] description: The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) defaultValue: '["/"]' -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix the nodes with the new project name. - defaultValue: false - parameterName: RefName type: String description: For internal use diff --git a/docs/collections/_reference/reference.v2.processors.tfsareaanditerationprocessor.md b/docs/collections/_reference/reference.v2.processors.tfsareaanditerationprocessor.md index b0c7b498b..25a262ab3 100644 --- a/docs/collections/_reference/reference.v2.processors.tfsareaanditerationprocessor.md +++ b/docs/collections/_reference/reference.v2.processors.tfsareaanditerationprocessor.md @@ -8,7 +8,6 @@ configurationSamples: { "$type": "TfsAreaAndIterationProcessorOptions", "Enabled": false, - "PrefixProjectToNodes": false, "NodeBasePaths": null, "AreaMaps": { "$type": "Dictionary`2" @@ -42,10 +41,6 @@ options: type: String[] description: missng XML code comments defaultValue: missng XML code comments -- parameterName: PrefixProjectToNodes - type: Boolean - description: Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - defaultValue: false - parameterName: ProcessorEnrichers 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. diff --git a/docs/collections/_reference/reference.v2.processors.workitemtrackingprocessor.md b/docs/collections/_reference/reference.v2.processors.workitemtrackingprocessor.md index 973ab8f00..50a22de61 100644 --- a/docs/collections/_reference/reference.v2.processors.workitemtrackingprocessor.md +++ b/docs/collections/_reference/reference.v2.processors.workitemtrackingprocessor.md @@ -9,7 +9,6 @@ configurationSamples: "$type": "WorkItemTrackingProcessorOptions", "Enabled": true, "ReplayRevisions": true, - "PrefixProjectToNodes": false, "CollapseRevisions": false, "WorkItemCreateRetryLimit": 5, "ProcessorEnrichers": [ @@ -39,10 +38,6 @@ options: 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: PrefixProjectToNodes - type: Boolean - description: missng XML code comments - defaultValue: missng XML code comments - parameterName: ProcessorEnrichers 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. diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs index 7e470e065..22acea11f 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs @@ -135,9 +135,7 @@ private KeyValuePair GetLastResortRemappingRule() var replaceEscapedSourceProjectName = _sourceProjectName.Replace("$", "$$"); var replaceEscapedTargetProjectName = _targetProjectName.Replace("$", "$$"); - _lastResortRemapRule = _Options.PrefixProjectToNodes - ? new KeyValuePair($"^{searchEscapedSourceProjectName}", $"{replaceEscapedTargetProjectName}\\{replaceEscapedSourceProjectName}") - : new KeyValuePair($"^{searchEscapedSourceProjectName}", $"{replaceEscapedTargetProjectName}"); + _lastResortRemapRule = new KeyValuePair($"^{searchEscapedSourceProjectName}", $"{replaceEscapedTargetProjectName}"); } return _lastResortRemapRule.Value; diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs index 621902369..eb3d176ac 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs @@ -9,12 +9,6 @@ public class TfsNodeStructureOptions : ProcessorEnricherOptions, ITfsNodeStructu public override Type ToConfigure => typeof(TfsNodeStructure); - /// - /// Prefix the nodes with the new project name. - /// - /// false - public bool PrefixProjectToNodes { get; set; } - /// /// The root paths of the Ares / Iterations you want migrate. See [NodeBasePath Configuration](#nodebasepath-configuration) /// @@ -44,7 +38,6 @@ public class TfsNodeStructureOptions : ProcessorEnricherOptions, ITfsNodeStructu public override void SetDefaults() { Enabled = true; - PrefixProjectToNodes = false; AreaMaps = new Dictionary(); IterationMaps = new Dictionary(); ShouldCreateMissingRevisionPaths = true; @@ -61,7 +54,6 @@ static public TfsNodeStructureOptions GetDefaults() public interface ITfsNodeStructureOptions { - public bool PrefixProjectToNodes { get; set; } public string[] NodeBasePaths { get; set; } public Dictionary AreaMaps { get; set; } public Dictionary IterationMaps { get; set; } diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs index aadc05ea5..b9c460010 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsWorkItemLinkEnricherOptions.cs @@ -27,9 +27,9 @@ public override void SetDefaults() SaveAfterEachLinkIsAdded = false; } - static public TfsNodeStructureOptions GetDefaults() + static public TfsWorkItemLinkEnricherOptions GetDefaults() { - var result = new TfsNodeStructureOptions(); + var result = new TfsWorkItemLinkEnricherOptions(); result.SetDefaults(); return result; } diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessor.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessor.cs index 726d4c2e0..e27359fd4 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessor.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessor.cs @@ -43,7 +43,6 @@ protected override void InternalExecute() { Enabled = true, NodeBasePaths = _options.NodeBasePaths, - PrefixProjectToNodes = _options.PrefixProjectToNodes, AreaMaps = _options.AreaMaps, IterationMaps = _options.IterationMaps, }; diff --git a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessorOptions.cs b/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessorOptions.cs index 2e8274ca7..56e143e54 100644 --- a/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessorOptions.cs +++ b/src/MigrationTools.Clients.AzureDevops.ObjectModel/Processors/TfsAreaAndIterationProcessorOptions.cs @@ -6,11 +6,6 @@ namespace MigrationTools.Processors { public class TfsAreaAndIterationProcessorOptions : ProcessorOptions, ITfsNodeStructureOptions { - /// - /// Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - /// - /// false - public bool PrefixProjectToNodes { get; set; } public string[] NodeBasePaths { get; set; } public Dictionary AreaMaps { get; set; } @@ -25,7 +20,6 @@ public override IProcessorOptions GetDefault() public override void SetDefaults() { - PrefixProjectToNodes = false; SourceName = "sourceName"; TargetName = "targetName"; AreaMaps = new Dictionary(); diff --git a/src/MigrationTools.Integration.Tests/AzureDevOpsObjectModelTests.cs b/src/MigrationTools.Integration.Tests/AzureDevOpsObjectModelTests.cs index 2236f40b9..7b9a5ced8 100644 --- a/src/MigrationTools.Integration.Tests/AzureDevOpsObjectModelTests.cs +++ b/src/MigrationTools.Integration.Tests/AzureDevOpsObjectModelTests.cs @@ -44,7 +44,6 @@ private static WorkItemTrackingProcessorOptions GetConfigurationTfsToTfsNoEnrich CollapseRevisions = false, ReplayRevisions = true, WorkItemCreateRetryLimit = 5, - PrefixProjectToNodes = false, SourceName = "Source", TargetName = "Target" }; diff --git a/src/MigrationTools.Samples/configuration.json b/src/MigrationTools.Samples/configuration.json index d1b61c00e..5d8c9af5f 100644 --- a/src/MigrationTools.Samples/configuration.json +++ b/src/MigrationTools.Samples/configuration.json @@ -122,7 +122,6 @@ { "ObjectType": "WorkItemMigrationConfig", "ReplayRevisions": true, - "PrefixProjectToNodes": false, "UpdateCreatedDate": true, "UpdateCreatedBy": true, "BuildFieldTable": false, diff --git a/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs b/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs index 1a6d29570..3d5d4a397 100644 --- a/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs +++ b/src/MigrationTools.Tests/Processors/WorkItemMigrationProcessorTests.cs @@ -25,7 +25,6 @@ public void WorkItemMigrationProcessorConfigureTest() CollapseRevisions = false, ReplayRevisions = true, WorkItemCreateRetryLimit = 5, - PrefixProjectToNodes = false, SourceName = "Source", TargetName = "Target" }; @@ -43,7 +42,6 @@ public void WorkItemMigrationProcessorRunTest() CollapseRevisions = false, ReplayRevisions = true, WorkItemCreateRetryLimit = 5, - PrefixProjectToNodes = false, SourceName = "Source", TargetName = "Target" }; diff --git a/src/MigrationTools/Processors/WorkItemProcessor/WorkItemTrackingProcessorOptions.cs b/src/MigrationTools/Processors/WorkItemProcessor/WorkItemTrackingProcessorOptions.cs index 5439c6f0b..67b84f38b 100644 --- a/src/MigrationTools/Processors/WorkItemProcessor/WorkItemTrackingProcessorOptions.cs +++ b/src/MigrationTools/Processors/WorkItemProcessor/WorkItemTrackingProcessorOptions.cs @@ -7,7 +7,6 @@ namespace MigrationTools.Processors public class WorkItemTrackingProcessorOptions : ProcessorOptions { public bool ReplayRevisions { get; set; } - public bool PrefixProjectToNodes { get; set; } public bool CollapseRevisions { get; set; } public int WorkItemCreateRetryLimit { get; set; } public override Type ToConfigure => typeof(WorkItemTrackingProcessor); @@ -23,7 +22,6 @@ public override void SetDefaults() CollapseRevisions = false; ReplayRevisions = true; WorkItemCreateRetryLimit = 5; - PrefixProjectToNodes = false; //Endpoints = new System.Collections.Generic.List() { // new InMemoryWorkItemEndpointOptions { Direction = EndpointDirection.Source }, // new InMemoryWorkItemEndpointOptions { Direction = EndpointDirection.Target } diff --git a/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs b/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs index ac89adff0..b085a72e1 100644 --- a/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs +++ b/src/MigrationTools/_EngineV1/Configuration/EngineConfigurationBuilder.cs @@ -287,7 +287,6 @@ public EngineConfiguration BuildDefault2() { Enabled = true, CollapseRevisions = false, - PrefixProjectToNodes = false, ReplayRevisions = true, WorkItemCreateRetryLimit = 5, ProcessorEnrichers = GetAllTypes(), diff --git a/src/MigrationTools/_EngineV1/Configuration/Processing/TeamMigrationConfig.cs b/src/MigrationTools/_EngineV1/Configuration/Processing/TeamMigrationConfig.cs index e2f26a87a..656c1e65e 100644 --- a/src/MigrationTools/_EngineV1/Configuration/Processing/TeamMigrationConfig.cs +++ b/src/MigrationTools/_EngineV1/Configuration/Processing/TeamMigrationConfig.cs @@ -7,12 +7,6 @@ public class TeamMigrationConfig : IProcessorConfig /// public bool Enabled { get; set; } - /// - /// Prefix your iterations and areas with the project name. If you have enabled this in `NodeStructuresMigrationConfig` you must do it here too. - /// - /// false - public bool PrefixProjectToNodes { get; set; } - /// /// Migrate original team settings after their creation on target team project /// diff --git a/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs b/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs index 42fa24841..c94039540 100644 --- a/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs +++ b/src/VstsSyncMigrator.Core.Tests/WorkItemMigrationTests.cs @@ -90,7 +90,6 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesEnabled_ { AreaMaps = new Dictionary(), IterationMaps = new Dictionary(), - PrefixProjectToNodes = true, }); string WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'SourceServer\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; @@ -145,7 +144,6 @@ public void TestFixAreaPath_WhenAreaPathInQuery_WithPrefixProjectToNodesEnabled_ { AreaMaps = new Dictionary(), IterationMaps = new Dictionary(), - PrefixProjectToNodes = true, }); var WIQLQueryBit = @"SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject AND [System.AreaPath] = 'Source Project\Area\Path1' AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')"; diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs index 4e3939a5e..17e559dc7 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/TestPlansAndSuitesMigrationContext.cs @@ -697,22 +697,12 @@ private void FixQueryForTeamProjectNameChange(ITestSuiteBase source, IDynamicTes Log.LogInformation("Team Project names dont match. We need to fix the query in dynamic test suite {0} - {1}.", source.Id, source.Title); Log.LogInformation("Replacing old project name {1} in query {0} with new team project name {2}", targetSuiteChild.Query.QueryText, source.Plan.Project.TeamProjectName, targetTestStore.Project.TeamProjectName); // First need to check is prefix project nodes has been applied for the migration - - if (_nodeStructureEnricher.Options.PrefixProjectToNodes) - { - // if prefix project nodes has been applied we need to take the original area/iteration value and prefix - targetSuiteChild.Query = - targetSuiteChild.Project.CreateTestQuery(targetSuiteChild.Query.QueryText.Replace( - string.Format(@"'{0}", source.Plan.Project.TeamProjectName), - string.Format(@"'{0}\{1}", targetTestStore.Project.TeamProjectName, source.Plan.Project.TeamProjectName))); - } - else - { + // If we are not profixing project nodes then we just need to take the old value for the project and replace it with the new project value targetSuiteChild.Query = targetSuiteChild.Project.CreateTestQuery(targetSuiteChild.Query.QueryText.Replace( string.Format(@"'{0}", source.Plan.Project.TeamProjectName), string.Format(@"'{0}", targetTestStore.Project.TeamProjectName))); - } + Log.LogInformation("New query is now {0}", targetSuiteChild.Query.QueryText); } } @@ -864,9 +854,7 @@ private void ProcessTestPlan(ITestPlan sourcePlan) var parameters = new Dictionary(); AddParameter("PlanId", parameters, sourcePlan.Id.ToString()); //////////////////////////////////// - var newPlanName = _nodeStructureEnricher.Options.PrefixProjectToNodes - ? $"{Engine.Source.WorkItems.GetProject().Name}-{sourcePlan.Name}" - : $"{sourcePlan.Name}"; + var newPlanName = $"{sourcePlan.Name}"; InnerLog(sourcePlan, $"Process Plan {newPlanName}", 0, true); var targetPlan = FindTestPlan(newPlanName, sourcePlan.Id); //if (targetPlan != null && TargetPlanContansTag(targetPlan.Id)) diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs index 9f494658b..ea0d56a64 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs @@ -136,7 +136,7 @@ private void ImportCommonEnricherConfigs() } // TfsNodeStructureOptions var wileConfig = _engineConfig.CommonEnrichersConfig.OfType().FirstOrDefault(); - if (revmanConfig == null) + if (wileConfig == null) { _workItemLinkEnricher.Configure(TfsWorkItemLinkEnricherOptions.GetDefaults()); Log.LogWarning("Default `TfsWorkItemLinkEnricherOptions` used... add a `TfsWorkItemLinkEnricherOptions` entry to `CommonEnrichersConfig` to customise the settings."); @@ -500,7 +500,7 @@ private void PopulateWorkItem(WorkItemData oldWi, WorkItemData newwit, string de var missedMigratedValue = oldWorkItem.Fields[f.ReferenceName].Value; if (missedMigratedValue != null && !string.Empty.Equals(missedMigratedValue)) { - Log.LogDebug("PopulateWorkItem:FieldUpdate: Missing field in target workitem, Source WorkItemId: {WorkitemId}, Field: {MissingField}, Value: {SourceValue}", oldWi.Id, f.ReferenceName, missedMigratedValue); + Log.LogWarning("PopulateWorkItem:FieldUpdate: Missing field in target workitem, Source WorkItemId: {WorkitemId}, Field: {MissingField}, Value: {SourceValue}", oldWi.Id, f.ReferenceName, missedMigratedValue); } continue; } @@ -508,8 +508,12 @@ private void PopulateWorkItem(WorkItemData oldWi, WorkItemData newwit, string de (!newWorkItem.Fields[f.ReferenceName].IsChangedInRevision || newWorkItem.Fields[f.ReferenceName].IsEditable) && oldWorkItem.Fields[f.ReferenceName].Value != newWorkItem.Fields[f.ReferenceName].Value) { - Log.LogDebug("PopulateWorkItem:FieldUpdate: {ReferenceName} | Old:{OldReferenceValue} New:{NewReferenceValue}", f.ReferenceName, oldWorkItem.Fields[f.ReferenceName].Value, newWorkItem.Fields[f.ReferenceName].Value); + Log.LogDebug("PopulateWorkItem:FieldUpdate: {ReferenceName} | Source:{OldReferenceValue} Target:{NewReferenceValue}", f.ReferenceName, oldWorkItem.Fields[f.ReferenceName].Value, newWorkItem.Fields[f.ReferenceName].Value); + newWorkItem.Fields[f.ReferenceName].Value = oldWorkItem.Fields[f.ReferenceName].Value; + } else + { + } } From ce11b180d52c437e7364331fcea0b3694a9f5e6b Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Mon, 5 Feb 2024 17:07:13 +0000 Subject: [PATCH 5/6] [Preview] Truncate LongText fields to 1m (#1903) This should truncate longTest fields to the max length and resolve #1861 --- .../MigrationContext/WorkItemMigrationContext.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs index ea0d56a64..2fa8d3441 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.TeamFoundation.Common; +using Microsoft.TeamFoundation.TestManagement; using Microsoft.TeamFoundation.WorkItemTracking.Client; using Microsoft.TeamFoundation.WorkItemTracking.Proxy; using Microsoft.TeamFoundation.WorkItemTracking.WebApi; @@ -511,10 +512,14 @@ private void PopulateWorkItem(WorkItemData oldWi, WorkItemData newwit, string de Log.LogDebug("PopulateWorkItem:FieldUpdate: {ReferenceName} | Source:{OldReferenceValue} Target:{NewReferenceValue}", f.ReferenceName, oldWorkItem.Fields[f.ReferenceName].Value, newWorkItem.Fields[f.ReferenceName].Value); newWorkItem.Fields[f.ReferenceName].Value = oldWorkItem.Fields[f.ReferenceName].Value; - } else - { - } + if (newWorkItem.Fields[f.ReferenceName].FieldDefinition.IsLongText) + { + // Truncate field to max length + newWorkItem.Fields[f.ReferenceName].Value = newWorkItem.Fields[f.ReferenceName].Value.ToString().Substring(0, 1000000); + } + + } } if (_nodeStructureEnricher.Options.Enabled) From 0b7e58b5aaa857be3c7a3c86ebed1a72482c2da7 Mon Sep 17 00:00:00 2001 From: "Martin Hinshelwood nkdAgility.com" Date: Tue, 6 Feb 2024 11:34:06 +0000 Subject: [PATCH 6/6] [Preview] Added fix to force set of ClosedDate again if it is missing for #1899 (#1909) --- .../MigrationContext/WorkItemMigrationContext.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs index 2fa8d3441..dd3a0b93c 100644 --- a/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs +++ b/src/VstsSyncMigrator.Core/Execution/MigrationContext/WorkItemMigrationContext.cs @@ -935,10 +935,19 @@ private void CheckClosedDateIsValid(WorkItemData sourceWorkItem, WorkItemData ta if (targetWorkItem.ToWorkItem().Fields.Contains("Microsoft.VSTS.Common.ClosedDate")) { closedDateField = "Microsoft.VSTS.Common.ClosedDate"; } + Log.LogDebug("CheckClosedDateIsValid::ClosedDate field is {closedDateField}", closedDateField); if (targetWorkItem.ToWorkItem().Fields[closedDateField].Value == null && (targetWorkItem.ToWorkItem().Fields["System.State"].Value.ToString() == "Closed" || targetWorkItem.ToWorkItem().Fields["System.State"].Value.ToString() == "Done")) { - Log.LogWarning("The field {closedDateField} is set to Null and will revert to the current date on save! ", closedDateField); - Log.LogWarning("Source Closed Date [#{sourceId}][Rev{sourceRev}]: {sourceClosedDate} ", sourceWorkItem.ToWorkItem().Id, sourceWorkItem.ToWorkItem().Rev, sourceWorkItem.ToWorkItem().Fields[closedDateField].Value); + if (sourceWorkItem.ToWorkItem().Fields.Contains(closedDateField) && sourceWorkItem.ToWorkItem().Fields[closedDateField].Value != null) + { + Log.LogDebug("CheckClosedDateIsValid::Setting Closed Date [#{sourceId}][Rev{sourceRev}]: {sourceClosedDate} ", sourceWorkItem.ToWorkItem().Id, sourceWorkItem.ToWorkItem().Rev, sourceWorkItem.ToWorkItem().Fields[closedDateField].Value); + targetWorkItem.ToWorkItem().Fields[closedDateField].Value = sourceWorkItem.ToWorkItem().Fields[closedDateField].Value; + } + else + { + Log.LogWarning("The field {closedDateField} is set to Null and will revert to the current date on save! ", closedDateField); + Log.LogWarning("Source Closed Date [#{sourceId}][Rev{sourceRev}]: {sourceClosedDate} ", sourceWorkItem.ToWorkItem().Id, sourceWorkItem.ToWorkItem().Rev, sourceWorkItem.ToWorkItem().Fields[closedDateField].Value); + } } if (!sourceWorkItem.ToWorkItem().Fields.Contains(closedDateField)) {