Skip to content

Commit 38ff5aa

Browse files
authored
TfsTeamSettings: Use user mapping when migrating team capacities (#2529)
PR affects `TfsTeamSettingsProcessor` and `TfsTeamSettingsTool`. The migrate team capacities, which tries to match source users to target users. I implemented user mapping into this process. - The processor and tool both had `MigrateCapacities` method. Which was copied from one to another one. I consolidated this method so there is only one and the processor and tool use this. - I split `MigrateCapacities` method into several simpler methods for better readability. - Lastly I implemented user mapping, so when the user from source is not found in target, also mapped user name is checked.
2 parents cd66dbd + 623f4e5 commit 38ff5aa

File tree

4 files changed

+205
-196
lines changed

4 files changed

+205
-196
lines changed

src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTeamSettingsProcessor.cs

Lines changed: 13 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -257,112 +257,23 @@ internal static string SwitchProjectName(string expressionString, string sourceP
257257
return string.Empty;
258258
}
259259

260-
private void MigrateCapacities(WorkHttpClient sourceHttpClient, WorkHttpClient targetHttpClient, TeamFoundationTeam sourceTeam, TeamFoundationTeam targetTeam, Dictionary<string, string> iterationMap)
260+
private void MigrateCapacities(
261+
WorkHttpClient sourceHttpClient,
262+
WorkHttpClient targetHttpClient,
263+
TeamFoundationTeam sourceTeam,
264+
TeamFoundationTeam targetTeam,
265+
Dictionary<string, string> iterationMap)
261266
{
262-
if (!Options.MigrateTeamCapacities) return;
263-
264-
Log.LogInformation("Migrating team capacities..");
265-
try
266-
{
267-
var sourceTeamContext = new TeamContext(Source.TfsProject.Guid, sourceTeam.Identity.TeamFoundationId);
268-
var sourceIterations = sourceHttpClient.GetTeamIterationsAsync(sourceTeamContext).ConfigureAwait(false).GetAwaiter().GetResult();
269-
270-
var targetTeamContext = new TeamContext(Target.TfsProject.Guid, targetTeam.Identity.TeamFoundationId);
271-
var targetIterations = targetHttpClient.GetTeamIterationsAsync(targetTeamContext).ConfigureAwait(false).GetAwaiter().GetResult();
272-
273-
foreach (var sourceIteration in sourceIterations)
274-
{
275-
try
276-
{
277-
var targetIterationPath = iterationMap[sourceIteration.Path];
278-
var targetIteration = targetIterations.FirstOrDefault(i => i.Path == targetIterationPath);
279-
if (targetIteration == null) continue;
280-
281-
var targetCapacities = new List<TeamMemberCapacityIdentityRef>();
282-
var sourceCapacities = sourceHttpClient.GetCapacitiesWithIdentityRefAsync(sourceTeamContext, sourceIteration.Id).ConfigureAwait(false).GetAwaiter().GetResult();
283-
foreach (var sourceCapacity in sourceCapacities)
284-
{
285-
var sourceDisplayName = sourceCapacity.TeamMember.DisplayName;
286-
var index = sourceDisplayName.IndexOf("<");
287-
if (index > 0)
288-
{
289-
sourceDisplayName = sourceDisplayName.Substring(0, index).Trim();
290-
}
291-
292-
// Match:
293-
// "Doe, John" to "Doe, John"
294-
// "John Doe" to "John Doe"
295-
var targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
296-
if (targetTeamFoundatationIdentity == null)
297-
{
298-
if (sourceDisplayName.Contains(", "))
299-
{
300-
// Match:
301-
// "Doe, John" to "John Doe"
302-
var splitName = sourceDisplayName.Split(',');
303-
sourceDisplayName = $"{splitName[1].Trim()} {splitName[0].Trim()}";
304-
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
305-
}
306-
else
307-
{
308-
if (sourceDisplayName.Contains(' '))
309-
{
310-
// Match:
311-
// "John Doe" to "Doe, John"
312-
var splitName = sourceDisplayName.Split(' ');
313-
sourceDisplayName = $"{splitName[1].Trim()}, {splitName[0].Trim()}";
314-
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
315-
}
316-
}
317-
318-
// last attempt to match on unique name
319-
// Match: "John Michael Bolden" to Bolden, "John Michael" on "[email protected]" unique name
320-
if (targetTeamFoundatationIdentity == null)
321-
{
322-
var sourceUniqueName = sourceCapacity.TeamMember.UniqueName;
323-
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.UniqueName == sourceUniqueName);
324-
}
325-
}
326-
327-
if (targetTeamFoundatationIdentity != null)
328-
{
329-
targetCapacities.Add(new TeamMemberCapacityIdentityRef
330-
{
331-
Activities = sourceCapacity.Activities,
332-
DaysOff = sourceCapacity.DaysOff,
333-
TeamMember = new IdentityRef
334-
{
335-
Id = targetTeamFoundatationIdentity.TeamFoundationId.ToString()
336-
}
337-
});
338-
}
339-
else
340-
{
341-
Log.LogWarning("[SKIP] Team Member {member} was not found on target when replacing capacities on iteration {iteration}.", sourceCapacity.TeamMember.DisplayName, targetIteration.Path);
342-
}
343-
}
344-
345-
if (targetCapacities.Count > 0)
346-
{
347-
targetHttpClient.ReplaceCapacitiesWithIdentityRefAsync(targetCapacities, targetTeamContext, targetIteration.Id).ConfigureAwait(false).GetAwaiter().GetResult();
348-
Log.LogDebug("Team {team} capacities for iteration {iteration} migrated.", targetTeam.Name, targetIteration.Path);
349-
}
350-
}
351-
catch (Exception ex)
352-
{
353-
Telemetry.TrackException(ex, null);
354-
Log.LogWarning(ex, "[SKIP] Problem migrating team capacities for iteration {iteration}.", sourceIteration.Path);
355-
}
356-
357-
}
358-
}
359-
catch (Exception ex)
267+
if (!Options.MigrateTeamCapacities)
360268
{
361-
Telemetry.TrackException(ex, null);
362-
Log.LogWarning(ex, "[SKIP] Problem migrating team capacities.");
269+
return;
363270
}
364271

365-
Log.LogInformation("Team capacities migration done..");
272+
TfsTeamSettingsTool.MigrateCapacities(
273+
sourceHttpClient, Source.TfsProject.Guid, sourceTeam,
274+
targetHttpClient, Target.TfsProject.Guid, targetTeam,
275+
iterationMap, _targetTeamFoundationIdentitiesLazyCache, Options.UseUserMapping,
276+
Telemetry, Log, exceptionLogLevel: LogLevel.Warning, Services);
366277
}
367278
}
368279
}

src/MigrationTools.Clients.TfsObjectModel/Processors/TfsTeamSettingsProcessorOptions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using MigrationTools.Processors.Infrastructure;
43

54
namespace MigrationTools.Processors
65
{
76
public class TfsTeamSettingsProcessorOptions : ProcessorOptions
87
{
9-
108
/// <summary>
119
/// Migrate original team settings after their creation on target team project
1210
/// </summary>
@@ -36,5 +34,11 @@ public class TfsTeamSettingsProcessorOptions : ProcessorOptions
3634
/// </summary>
3735
public List<string> Teams { get; set; }
3836

37+
/// <summary>
38+
/// Use user mapping file from TfsTeamSettingsTool when matching users when migrating capacities.
39+
/// By default, users in source are matched in target users by current display name. When this is set to `true`,
40+
/// users are matched also by mapped name from user mapping file.
41+
/// </summary>
42+
public bool UseUserMapping { get; set; }
3943
}
40-
}
44+
}

0 commit comments

Comments
 (0)