Skip to content

Commit

Permalink
TfsTeamSettings: Use user mapping when migrating team capacities (#2529)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MrHinsh authored Nov 25, 2024
2 parents cd66dbd + 623f4e5 commit 38ff5aa
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,112 +257,23 @@ internal static string SwitchProjectName(string expressionString, string sourceP
return string.Empty;
}

private void MigrateCapacities(WorkHttpClient sourceHttpClient, WorkHttpClient targetHttpClient, TeamFoundationTeam sourceTeam, TeamFoundationTeam targetTeam, Dictionary<string, string> iterationMap)
private void MigrateCapacities(
WorkHttpClient sourceHttpClient,
WorkHttpClient targetHttpClient,
TeamFoundationTeam sourceTeam,
TeamFoundationTeam targetTeam,
Dictionary<string, string> iterationMap)
{
if (!Options.MigrateTeamCapacities) return;

Log.LogInformation("Migrating team capacities..");
try
{
var sourceTeamContext = new TeamContext(Source.TfsProject.Guid, sourceTeam.Identity.TeamFoundationId);
var sourceIterations = sourceHttpClient.GetTeamIterationsAsync(sourceTeamContext).ConfigureAwait(false).GetAwaiter().GetResult();

var targetTeamContext = new TeamContext(Target.TfsProject.Guid, targetTeam.Identity.TeamFoundationId);
var targetIterations = targetHttpClient.GetTeamIterationsAsync(targetTeamContext).ConfigureAwait(false).GetAwaiter().GetResult();

foreach (var sourceIteration in sourceIterations)
{
try
{
var targetIterationPath = iterationMap[sourceIteration.Path];
var targetIteration = targetIterations.FirstOrDefault(i => i.Path == targetIterationPath);
if (targetIteration == null) continue;

var targetCapacities = new List<TeamMemberCapacityIdentityRef>();
var sourceCapacities = sourceHttpClient.GetCapacitiesWithIdentityRefAsync(sourceTeamContext, sourceIteration.Id).ConfigureAwait(false).GetAwaiter().GetResult();
foreach (var sourceCapacity in sourceCapacities)
{
var sourceDisplayName = sourceCapacity.TeamMember.DisplayName;
var index = sourceDisplayName.IndexOf("<");
if (index > 0)
{
sourceDisplayName = sourceDisplayName.Substring(0, index).Trim();
}

// Match:
// "Doe, John" to "Doe, John"
// "John Doe" to "John Doe"
var targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
if (targetTeamFoundatationIdentity == null)
{
if (sourceDisplayName.Contains(", "))
{
// Match:
// "Doe, John" to "John Doe"
var splitName = sourceDisplayName.Split(',');
sourceDisplayName = $"{splitName[1].Trim()} {splitName[0].Trim()}";
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
}
else
{
if (sourceDisplayName.Contains(' '))
{
// Match:
// "John Doe" to "Doe, John"
var splitName = sourceDisplayName.Split(' ');
sourceDisplayName = $"{splitName[1].Trim()}, {splitName[0].Trim()}";
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.DisplayName == sourceDisplayName);
}
}

// last attempt to match on unique name
// Match: "John Michael Bolden" to Bolden, "John Michael" on "john.m.bolden@example.com" unique name
if (targetTeamFoundatationIdentity == null)
{
var sourceUniqueName = sourceCapacity.TeamMember.UniqueName;
targetTeamFoundatationIdentity = _targetTeamFoundationIdentitiesLazyCache.Value.FirstOrDefault(i => i.UniqueName == sourceUniqueName);
}
}

if (targetTeamFoundatationIdentity != null)
{
targetCapacities.Add(new TeamMemberCapacityIdentityRef
{
Activities = sourceCapacity.Activities,
DaysOff = sourceCapacity.DaysOff,
TeamMember = new IdentityRef
{
Id = targetTeamFoundatationIdentity.TeamFoundationId.ToString()
}
});
}
else
{
Log.LogWarning("[SKIP] Team Member {member} was not found on target when replacing capacities on iteration {iteration}.", sourceCapacity.TeamMember.DisplayName, targetIteration.Path);
}
}

if (targetCapacities.Count > 0)
{
targetHttpClient.ReplaceCapacitiesWithIdentityRefAsync(targetCapacities, targetTeamContext, targetIteration.Id).ConfigureAwait(false).GetAwaiter().GetResult();
Log.LogDebug("Team {team} capacities for iteration {iteration} migrated.", targetTeam.Name, targetIteration.Path);
}
}
catch (Exception ex)
{
Telemetry.TrackException(ex, null);
Log.LogWarning(ex, "[SKIP] Problem migrating team capacities for iteration {iteration}.", sourceIteration.Path);
}

}
}
catch (Exception ex)
if (!Options.MigrateTeamCapacities)
{
Telemetry.TrackException(ex, null);
Log.LogWarning(ex, "[SKIP] Problem migrating team capacities.");
return;
}

Log.LogInformation("Team capacities migration done..");
TfsTeamSettingsTool.MigrateCapacities(
sourceHttpClient, Source.TfsProject.Guid, sourceTeam,
targetHttpClient, Target.TfsProject.Guid, targetTeam,
iterationMap, _targetTeamFoundationIdentitiesLazyCache, Options.UseUserMapping,
Telemetry, Log, exceptionLogLevel: LogLevel.Warning, Services);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using MigrationTools.Processors.Infrastructure;

namespace MigrationTools.Processors
{
public class TfsTeamSettingsProcessorOptions : ProcessorOptions
{

/// <summary>
/// Migrate original team settings after their creation on target team project
/// </summary>
Expand Down Expand Up @@ -36,5 +34,11 @@ public class TfsTeamSettingsProcessorOptions : ProcessorOptions
/// </summary>
public List<string> Teams { get; set; }

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

0 comments on commit 38ff5aa

Please sign in to comment.