Skip to content

Commit 9f79e9a

Browse files
authored
DOCS UPDATE: Modernize area and iteration maps how-to guide with current configuration format (#2874)
The how-to guide for creating area and iteration maps was significantly outdated compared to the reference documentation. This update brings the guide up to date with the current configuration schema and best practices. ## Key Changes Made ### ✅ Updated Configuration Format - Migrated from deprecated `IterationMaps`/`AreaMaps` dictionary format to modern `Iterations.Mappings`/`Areas.Mappings` structured format - Added clear examples of the new Match/Replacement property structure - Maintained backward compatibility documentation while marking old format as deprecated ### ✅ Enhanced Documentation Structure - Added dedicated sections for Configuration Format, Regular Expressions, and Mapping Patterns - Reorganized content with better headings and logical flow - Added comprehensive working examples users can copy and adapt ### ✅ Added Missing Critical Content - **Important Warning**: Added prominent note that work items cannot be migrated if Area/Iteration paths don't exist on target - **Special Character Escaping**: Added detailed warnings about JSON and regex escaping requirements - **Filters Documentation**: Explained how to use Filters with glob patterns - **PrefixProjectToNodes Replacement**: Showed how to replace deprecated option with explicit mappings ### ✅ Practical Examples Added - Simple project rename with path preservation - Project rename to root only (hierarchy flattening) - Replacing deprecated PrefixProjectToNodes functionality - Using Filters to control node migration - Complete TfsNodeStructureToolOptions configuration example ### ✅ Better User Guidance - Links to regex101.com for testing regular expressions - Advice on terminating backslashes in patterns - Clear explanation of back-references in replacements - Cross-references to complete reference documentation ## Before vs After **Before**: Used outdated dictionary format with minimal explanation ```json "IterationMaps": { "OldProject\\Iteration": "NewProject\\Sprint" } ``` **After**: Uses modern structured format with comprehensive guidance ```json "Iterations": { "Mappings": [ { "Match": "^OldProject\\\\(.*)$", "Replacement": "NewProject\\$1" } ] } ``` The how-to guide now provides practical, actionable guidance that matches the current tool capabilities and configuration schema exactly as shown in the reference documentation. ![Regular Expression Example](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8) Fixes #2873. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `esm.ubuntu.com` > - Triggering command: `/usr/lib/apt/methods/https` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to my [firewall allow list](https://gh.io/copilot/firewall-config) > > </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
2 parents 277308d + 86748e6 commit 9f79e9a

File tree

2 files changed

+196
-19
lines changed

2 files changed

+196
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,5 @@ $RECYCLE.BIN/
164164
logs/
165165
/staging/
166166
/public/
167+
/docs/public/
167168
/docs/data/generated

docs/content/docs/how-to/creating-iteration-and-area-maps/index.md

Lines changed: 195 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,40 @@ date: 2025-06-24T12:07:31Z
99
---
1010
As per the [documentation]({{< ref "docs/reference/tools/tfs-node-structure-tool" >}}), you need to add Iteration Maps and Area Maps that adapt the old locations to new ones that are valid in the Target.
1111

12+
**NOTE: It is NOT possible to migrate a work item if the Area or Iteration path does not exist on the target project. This is because the work item will be created with the same Area and Iteration path as the source work item. If the path does not exist, the work item will not be created. _There is no way around this!_**
13+
1214
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 you have `"StopMigrationOnMissingAreaIterationNodes": true,` set, the migration will stop just after it outputs a list of the missing nodes.
1315

1416
Our algorithm that converts the Source nodes to Target nodes processes the [mappings]({{< ref "docs/reference/tools/tfs-node-structure-tool" >}}) 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.
1517

1618
> We recently updated the logging for this part of the system to more easily debug both your mappings and to see what the system is doing with the nodes and their current state. You can set `"LogLevel": "Debug"` to see the details.
1719
18-
To add a mapping, you can follow [the documentation]({{< ref "docs/reference/tools/tfs-node-structure-tool" >}}) with this being the simplest way:
20+
## Configuration Format
21+
22+
The modern configuration uses structured mappings with `Match` and `Replacement` properties, utilizing the new `Iterations.Mappings` and `Areas.Mappings` format:
23+
24+
```json
25+
"Iterations": {
26+
"Mappings": [
27+
{
28+
"Match": "WorkItemMovedFromProjectName\\\\Iteration 1",
29+
"Replacement": "TargetProject\\Sprint 1"
30+
}
31+
]
32+
},
33+
"Areas": {
34+
"Mappings": [
35+
{
36+
"Match": "WorkItemMovedFromProjectName\\\\Team 2",
37+
"Replacement": "TargetProject\\ProductA\\Team 2"
38+
}
39+
]
40+
}
41+
```
42+
43+
### Legacy Format Support
44+
45+
_Note: The old `IterationMaps` and `AreaMaps` dictionary format is still supported for backward compatibility but is deprecated:_
1946

2047
```json
2148
"IterationMaps": {
@@ -26,50 +53,199 @@ To add a mapping, you can follow [the documentation]({{< ref "docs/reference/too
2653
}
2754
```
2855

29-
Or you can use regular expressions to match the missing area or iteration paths:
56+
## Using Regular Expressions
57+
58+
You can use regular expressions to match and transform area or iteration paths. The syntax uses structured mappings with regular expression patterns:
3059

3160
```json
32-
"IterationMaps": {
33-
"^OriginalProject\\\\Path1(?=\\\\Sprint 2022)": "TargetProject\\AnotherPath\\NewTeam",
34-
"^OriginalProject\\\\Path1(?=\\\\Sprint 2020)": "TargetProject\\AnotherPath\\Archives\\Sprints 2020",
35-
"^OriginalProject\\\\Path2": "TargetProject\\YetAnotherPath\\Path2"
61+
"Iterations": {
62+
"Mappings": [
63+
{
64+
"Match": "^OriginalProject\\\\Path1(?=\\\\Sprint 2022)(.*)$",
65+
"Replacement": "TargetProject\\AnotherPath\\NewTeam$1"
66+
},
67+
{
68+
"Match": "^OriginalProject\\\\Path1(?=\\\\Sprint 2020)(.*)$",
69+
"Replacement": "TargetProject\\AnotherPath\\Archives\\Sprints 2020$1"
70+
},
71+
{
72+
"Match": "^OriginalProject\\\\Path2(.*)$",
73+
"Replacement": "TargetProject\\YetAnotherPath\\Path2$1"
74+
}
75+
]
3676
},
37-
"AreaMaps": {
38-
"^OriginalProject\\\\(DescopeThis|DescopeThat)": "TargetProject\\Archive\\Descoped\\",
39-
"^OriginalProject\\\\(?!DescopeThis|DescopeThat)": "TargetProject\\NewArea\\"
77+
"Areas": {
78+
"Mappings": [
79+
{
80+
"Match": "^OriginalProject\\\\(DescopeThis|DescopeThat)(.*)$",
81+
"Replacement": "TargetProject\\Archive\\Descoped\\$1$2"
82+
},
83+
{
84+
"Match": "^OriginalProject\\\\(?!DescopeThis|DescopeThat)(.*)$",
85+
"Replacement": "TargetProject\\NewArea\\$1"
86+
}
87+
]
4088
}
4189
```
4290

43-
If you want to use the matches in the replacement, you can use the following:
91+
### Using Back-References in Replacements
92+
93+
If you want to use captured groups in the replacement, you can reference them using `$` followed by the group number:
4494

4595
```json
46-
"IterationMaps": {
47-
"^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$": "TargetProject\\Q1$2"
96+
"Iterations": {
97+
"Mappings": [
98+
{
99+
"Match": "^\\\\oldproject1(?:\\\\([^\\\\]+))?\\\\([^\\\\]+)$",
100+
"Replacement": "TargetProject\\Q1\\$2"
101+
}
102+
]
48103
}
49104
```
50105

51106
If the old iteration path was `\oldproject1\Custom Reporting\Sprint 13`, 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 the number of the match.
52107

108+
### Important Notes About Escaping
109+
53110
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 ChatGPT.
54111

55-
_NOTE: You need `\\` to escape a `\` in the pattern, and `\\` to escape a `\` in JSON. Therefore, on the left of the match, you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the match._
112+
**Special Character Escaping Warning:** Special characters in the acceptation of regular expressions _and_ JSON both need to be escaped. For the Match property, 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 property, 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.
113+
114+
**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.
115+
116+
_NOTE: You need `\\` to escape a `\` in the pattern, and `\\` to escape a `\` in JSON. Therefore, in the Match property you need 4 `\` to represent the `\\` for the pattern and only 2 `\` in the Replacement property._
117+
118+
## Some Useful Mapping Patterns
119+
120+
### Simple Project Rename with Path Preservation
121+
122+
```json
123+
"Iterations": {
124+
"Mappings": [
125+
{
126+
"Match": "^OldProjectName([\\\\]?.*)$",
127+
"Replacement": "NewProjectName$1"
128+
}
129+
]
130+
},
131+
"Areas": {
132+
"Mappings": [
133+
{
134+
"Match": "^OldProjectName([\\\\]?.*)$",
135+
"Replacement": "NewProjectName$1"
136+
}
137+
]
138+
}
139+
```
140+
141+
This maps all `OldProjectName` area or iterations to a similar new node, preserving the entire path structure. If you have `ShouldCreateMissingRevisionPaths` enabled, it will create missing nodes automatically.
56142

57-
## Some pretty cool mappings
143+
### Project Rename to Root Only
58144

59145
```json
60-
"^OldProjectName([\\\\]?.*)$": "NewProjectName$1"
146+
"Iterations": {
147+
"Mappings": [
148+
{
149+
"Match": "^OldProjectName([\\\\]?.*)$",
150+
"Replacement": "NewProjectName"
151+
}
152+
]
153+
},
154+
"Areas": {
155+
"Mappings": [
156+
{
157+
"Match": "^OldProjectName([\\\\]?.*)$",
158+
"Replacement": "NewProjectName"
159+
}
160+
]
161+
}
61162
```
62163

63-
or
164+
This maps all `OldProjectName` paths to just the new project name root, effectively flattening the hierarchy.
165+
166+
### Replacing PrefixProjectToNodes
167+
168+
The deprecated `PrefixProjectToNodes` option can be replaced with explicit mappings:
64169

65170
```json
66-
"^OldProjectName([\\\\]?.*)$": "NewProjectName"
171+
"Iterations": {
172+
"Mappings": [
173+
{
174+
"Match": "^SourceServer\\\\(.*)$",
175+
"Replacement": "TargetServer\\SourceServer\\$1"
176+
}
177+
]
178+
},
179+
"Areas": {
180+
"Mappings": [
181+
{
182+
"Match": "^SourceServer\\\\(.*)$",
183+
"Replacement": "TargetServer\\SourceServer\\$1"
184+
}
185+
]
186+
}
67187
```
68188

69-
The first one maps all `OldProjectName` area or iterations to a similar new node. If you have `CreateMissingNodes` enabled, it will create that. The second will just map all `OldProjectName` to the new project name root.
189+
This prepends the source project name to the target set of nodes, useful when the target project already has nodes and you don't want to merge them together.
190+
191+
## Using Filters
70192

71-
![image](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8)
193+
You can also use `Filters` to control which nodes are migrated before applying mappings:
72194

195+
```json
196+
"Iterations": {
197+
"Filters": ["*\\Sprint*"],
198+
"Mappings": [
199+
{
200+
"Match": "^OriginalProject\\\\(.*)$",
201+
"Replacement": "TargetProject\\$1"
202+
}
203+
]
204+
},
205+
"Areas": {
206+
"Filters": ["*\\Team 2", "Team 2\\*"],
207+
"Mappings": [
208+
{
209+
"Match": "^OriginalProject\\\\(.*)$",
210+
"Replacement": "TargetProject\\$1"
211+
}
212+
]
213+
}
73214
```
74215

216+
Filters use glob patterns and are applied before mappings. You can exclude specific paths by prefixing with `!`.
217+
218+
![Regular Expression Example](https://github.com/nkdAgility/azure-devops-migration-tools/assets/5205575/2cf50929-7ea9-4a71-beab-dd8ff3b5b2a8)
219+
220+
## Complete Example Configuration
221+
222+
Here's a complete example showing the TfsNodeStructureTool configuration with both Areas and Iterations mappings:
223+
224+
```json
225+
{
226+
"$type": "TfsNodeStructureToolOptions",
227+
"Enabled": true,
228+
"Areas": {
229+
"Filters": [],
230+
"Mappings": [
231+
{
232+
"Match": "^Skypoint Cloud$",
233+
"Replacement": "MigrationTest5"
234+
}
235+
]
236+
},
237+
"Iterations": {
238+
"Filters": [],
239+
"Mappings": [
240+
{
241+
"Match": "^Skypoint Cloud\\\\Sprint 1$",
242+
"Replacement": "MigrationTest5\\Sprint 1"
243+
}
244+
]
245+
},
246+
"ShouldCreateMissingRevisionPaths": true,
247+
"ReplicateAllExistingNodes": true
248+
}
75249
```
250+
251+
For more detailed information and advanced configuration options, refer to the complete [TFS Node Structure Tool documentation]({{< ref "docs/reference/tools/tfs-node-structure-tool" >}}).

0 commit comments

Comments
 (0)