Skip to content

Commit 0ad8515

Browse files
authored
Experiment: Create Nodes (Area\Iteration) at validation and not up front. (#1733)
* Node creation on demand! * Add flag for this configuration +semver: minor
1 parent 97de284 commit 0ad8515

File tree

12 files changed

+136
-22
lines changed

12 files changed

+136
-22
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ We use these tools with our customers, and for fun, to do real world migrations
7979

8080
## Change Log
8181

82+
- 14.3 - Created a flag for `ShouldCreateNodesUpFront`, while the default is `true` this is a private preview of a new feature. Instead of creating the area and iteration paths up front, this new feature instead creates the Area & Iteration paths at validation time. For each missing path it will create it, and for those that exist it will simply pass over after a `GetNodeFromPath` call. For tose wishing to participate in the preview please set `ShouldCreateNodesUpFront` to `false`, and to create the nodes rather than just list them set `ShouldCreateMissingRevisionPaths` to `true` as well. It will still list nodes that are not able to be created and require a mapping. NOTE: You can also run with `"ShouldCreateMissingRevisionPaths": false` to list all the nodes that will be created so that you can create more elaborate mappings.
8283
- 14.2 - Removed the `StopMigrationOnMissingAreaIterationNodes` flag. All missing nodes MUST be present or mapped using `AreaMaps` and `IterationMaps`. System will always stop on missing nodes.
8384
- 14.1 - Enabled auto update on client devices, server still used choco
8485
- 14.0 - Move from Chocolaty to Winget as the base installer. We will continue to publish to Chocolaty, but we recommend using `winget install nkdAgility.AzureDevOpsMigrationTools` for future installs. Main executable renamed to "devopsmigration.exe" to prevent conflict with other applications with symbolic links.

docs/Reference/Generated/MigrationTools.Host.xml

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/_data/reference.v1.processors.workitemmigrationcontext.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ configurationSamples:
1717
"AttachmentMigration": true,
1818
"AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\",
1919
"FixHtmlAttachmentLinks": false,
20-
"SkipToFinalRevisedWorkItemType": true,
20+
"SkipToFinalRevisedWorkItemType": false,
2121
"WorkItemCreateRetryLimit": 5,
2222
"FilterWorkItemsThatAlreadyExistInTarget": true,
2323
"PauseAfterEachWorkItem": false,
@@ -38,7 +38,8 @@ configurationSamples:
3838
"MaxGracefulFailures": 0,
3939
"SkipRevisionWithInvalidIterationPath": false,
4040
"SkipRevisionWithInvalidAreaPath": false,
41-
"ShouldCreateMissingRevisionPaths": true
41+
"ShouldCreateMissingRevisionPaths": true,
42+
"ShouldCreateNodesUpFront": true
4243
}
4344
sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemMigrationConfig
4445
description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure.
@@ -122,6 +123,10 @@ options:
122123
type: Boolean
123124
description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions.
124125
defaultValue: missng XML code comments
126+
- parameterName: ShouldCreateNodesUpFront
127+
type: Boolean
128+
description: missng XML code comments
129+
defaultValue: missng XML code comments
125130
- parameterName: SkipRevisionWithInvalidAreaPath
126131
type: Boolean
127132
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.

docs/_data/reference.v2.processorenrichers.tfsnodestructure.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ configurationSamples:
1515
"IterationMaps": {
1616
"$type": "Dictionary`2"
1717
},
18-
"ShouldCreateMissingRevisionPaths": true
18+
"ShouldCreateMissingRevisionPaths": true,
19+
"ShouldCreateNodesUpFront": true
1920
}
2021
sampleFor: MigrationTools.Enrichers.TfsNodeStructureOptions
2122
description: missng XML code comments
@@ -51,6 +52,10 @@ options:
5152
type: Boolean
5253
description: missng XML code comments
5354
defaultValue: missng XML code comments
55+
- parameterName: ShouldCreateNodesUpFront
56+
type: Boolean
57+
description: missng XML code comments
58+
defaultValue: missng XML code comments
5459
status: missng XML code comments
5560
processingTarget: missng XML code comments
5661
classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs

docs/collections/_reference/reference.v1.processors.workitemmigrationcontext.md

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ configurationSamples:
1818
"AttachmentMigration": true,
1919
"AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\",
2020
"FixHtmlAttachmentLinks": false,
21-
"SkipToFinalRevisedWorkItemType": true,
21+
"SkipToFinalRevisedWorkItemType": false,
2222
"WorkItemCreateRetryLimit": 5,
2323
"FilterWorkItemsThatAlreadyExistInTarget": true,
2424
"PauseAfterEachWorkItem": false,
@@ -39,7 +39,8 @@ configurationSamples:
3939
"MaxGracefulFailures": 0,
4040
"SkipRevisionWithInvalidIterationPath": false,
4141
"SkipRevisionWithInvalidAreaPath": false,
42-
"ShouldCreateMissingRevisionPaths": true
42+
"ShouldCreateMissingRevisionPaths": true,
43+
"ShouldCreateNodesUpFront": true
4344
}
4445
sampleFor: MigrationTools._EngineV1.Configuration.Processing.WorkItemMigrationConfig
4546
description: WorkItemMigrationConfig is the main processor used to Migrate Work Items, Links, and Attachments. Use `WorkItemMigrationConfig` to configure.
@@ -123,6 +124,10 @@ options:
123124
type: Boolean
124125
description: When set to True the susyem will try to create any missing missing area or iteration paths from the revisions.
125126
defaultValue: missng XML code comments
127+
- parameterName: ShouldCreateNodesUpFront
128+
type: Boolean
129+
description: missng XML code comments
130+
defaultValue: missng XML code comments
126131
- parameterName: SkipRevisionWithInvalidAreaPath
127132
type: Boolean
128133
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.
@@ -440,6 +445,76 @@ topics:
440445
`TargetProject\NewArea\ValidArea\` but `OriginalProject\DescopeThis` would not
441446
be modified by this rule.
442447
448+
### More Complex Regex
449+
450+
451+
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.
452+
453+
454+
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.
455+
456+
457+
> 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.
458+
459+
460+
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:
461+
462+
463+
```
464+
465+
"IterationMaps": {
466+
"WorkItemMovedFromProjectName\\\\Iteration 1": "TargetProject\\Sprint 1",
467+
},
468+
469+
"AreaMaps": {
470+
"WorkItemMovedFromProjectName\\\\Team 2": "TargetProject\\ProductA\\Team 2",
471+
}
472+
473+
```
474+
475+
Or you can use regular expressions to match the missing area or iteration paths:
476+
477+
478+
```
479+
480+
"IterationMaps": {
481+
"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",
482+
"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",
483+
"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2",
484+
},
485+
486+
"AreaMaps": {
487+
"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",
488+
"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\",
489+
}
490+
491+
```
492+
493+
494+
If you want to use the matches in the replacement you can use the following:
495+
496+
497+
```
498+
499+
"IterationMaps": {
500+
"^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1\$2",
501+
}
502+
503+
```
504+
505+
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.
506+
507+
508+
509+
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.
510+
511+
512+
_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_
513+
514+
515+
![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8)
516+
517+
443518
## More Complex Team Migrations
444519
445520
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`.

docs/collections/_reference/reference.v2.processorenrichers.tfsnodestructure.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ configurationSamples:
1616
"IterationMaps": {
1717
"$type": "Dictionary`2"
1818
},
19-
"ShouldCreateMissingRevisionPaths": true
19+
"ShouldCreateMissingRevisionPaths": true,
20+
"ShouldCreateNodesUpFront": true
2021
}
2122
sampleFor: MigrationTools.Enrichers.TfsNodeStructureOptions
2223
description: missng XML code comments
@@ -52,6 +53,10 @@ options:
5253
type: Boolean
5354
description: missng XML code comments
5455
defaultValue: missng XML code comments
56+
- parameterName: ShouldCreateNodesUpFront
57+
type: Boolean
58+
description: missng XML code comments
59+
defaultValue: missng XML code comments
5560
status: missng XML code comments
5661
processingTarget: missng XML code comments
5762
classFile: /src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs

src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructure.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,15 @@ private NodeInfo GetOrCreateNode(string nodePath, DateTime? startDate, DateTime?
170170
_pathToKnownNodeMap.TryGetValue(currentAncestorPath, out parentNode);
171171
if (parentNode == null)
172172
{
173-
parentNode= _targetCommonStructureService.GetNodeFromPath(currentAncestorPath);
173+
try
174+
{
175+
parentNode = _targetCommonStructureService.GetNodeFromPath(currentAncestorPath);
176+
} catch (Exception ex)
177+
{
178+
Log.LogDebug(" Not Found:", currentAncestorPath);
179+
parentNode = null;
180+
}
181+
174182
}
175183
}
176184
else
@@ -251,7 +259,10 @@ public override void ProcessorExecutionBegin(IProcessor processor)
251259
{
252260
Log.LogInformation("Migrating all Nodes before the Processor run.");
253261
EntryForProcessorType(processor);
254-
MigrateAllNodeStructures();
262+
if (Options.ShouldCreateNodesUpFront) // FLAG for Expermiment to Do on Demand.
263+
{
264+
MigrateAllNodeStructures();
265+
}
255266
RefreshForProcessorType(processor);
256267
}
257268
}

src/MigrationTools.Clients.AzureDevops.ObjectModel/ProcessorEnrichers/TfsNodeStructureOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ public class TfsNodeStructureOptions : ProcessorEnricherOptions, ITfsNodeStructu
1212
public Dictionary<string, string> AreaMaps { get; set; }
1313
public Dictionary<string, string> IterationMaps { get; set; }
1414
public bool ShouldCreateMissingRevisionPaths { get; set; }
15+
public bool ShouldCreateNodesUpFront { get; set; } // FLAG for Expermiment to Do on Demand.
1516

1617
public override void SetDefaults()
1718
{
1819
Enabled = true;
1920
AreaMaps = new Dictionary<string, string>();
2021
IterationMaps = new Dictionary<string, string>();
2122
ShouldCreateMissingRevisionPaths = true;
23+
ShouldCreateNodesUpFront = true; // FLAG for Expermiment to Do on Demand.
2224
}
2325
}
2426

src/MigrationTools.Host/MigrationToolHost.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public static IHostBuilder CreateDefaultBuilder(string[] args)
151151
private static string GetVersionTextForLog()
152152
{
153153
Version runningVersion = DetectVersionService2.GetRunningVersion();
154-
string textVersion = ((runningVersion.Major > 1) ? "$v" + runningVersion : ThisAssembly.Git.BaseTag + "-" + ThisAssembly.Git.Commits + "-local");
154+
string textVersion = ((runningVersion.Major > 1) ? "v" + runningVersion : ThisAssembly.Git.BaseTag + "-" + ThisAssembly.Git.Commits + "-local");
155155
return textVersion;
156156
}
157157

src/MigrationTools.Host/StartupService.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ public void RunStartupLogic(string[] args)
7373
Log.Warning("Windows Server: If you are running on Windows Server you can use the experimental version of Winget, or you can still use Chocolatey to manage the install. Install chocolatey from https://chocolatey.org/install and then use `choco install vsts-sync-migrator` to install, and `choco upgrade vsts-sync-migrator` to upgrade to newer versions.", _detectVersionService.PackageId);
7474
} else
7575
{
76-
if (!_detectVersionService.IsPackageInstalled && !_detectVersionService.IsRunningInDebug)
76+
if (!_detectVersionService.IsRunningInDebug)
77+
{
78+
if (!_detectVersionService.IsPackageInstalled)
7779
{
7880
Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version?");
7981
Console.WriteLine("Do you want install the managed version? (y/n)");
@@ -85,14 +87,14 @@ public void RunStartupLogic(string[] args)
8587
}
8688
if (_detectVersionService.IsUpdateAvailable && _detectVersionService.IsPackageInstalled)
8789
{
88-
Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version from Winget?");
90+
Log.Information("It looks like an updated version is available from Winget, would you like to update?");
8991
Console.WriteLine("Do you want install the managed version? (y/n)");
9092
if (Console.ReadKey().Key == ConsoleKey.Y)
9193
{
9294
_detectVersionService.UpdateFromSource();
9395
}
9496
}
95-
if ((_detectVersionService.IsNewLocalVersionAvailable && _detectVersionService.IsPackageInstalled) && !_detectVersionService.IsRunningInDebug)
97+
if (_detectVersionService.IsNewLocalVersionAvailable && _detectVersionService.IsPackageInstalled)
9698
{
9799
Log.Information("It looks like this package ({PackageId}) has been updated locally to version {InstalledVersion} and you are not running the latest version?", _detectVersionService.PackageId, _detectVersionService.InstalledVersion);
98100
Console.WriteLine("Do you want to quit and restart? (y/n)");
@@ -104,6 +106,10 @@ public void RunStartupLogic(string[] args)
104106
Environment.Exit(0);
105107
}
106108
}
109+
} else
110+
{
111+
Log.Information("Running in Debug! No further version checkes.....");
112+
}
107113
}
108114
} else
109115
{

0 commit comments

Comments
 (0)