Skip to content

Commit cd66dbd

Browse files
authored
TfsUserMappingTool: make user map public, fix user mapping (de)serialization (#2528)
User mapping was defined in `TfsUserMappingTool._UserMappings` _private_ field and used only in one place by `TfsUserMappingTool.MapUserIdentityField` method. I believe, that user mapping can be used in some other places, for example users are loaded when migrating team backlog capacities. Now it triggers a lot of warning for us, because users in source are not the same as users in target. So I want to use user mapping also in this case. (I do not know yet, but maybe there are more places where user mapping can be used.) So this PR makes public property `TfsUserMappingTool.UserMappings` as a lazy dictionary, that can be used later anywhere. During this, I noticed a bug, which means, that probably no-one is using user mappings. The problem is, that user mapping JSON file was generated in `TfsExportUsersForMappingProcessor` and it serialized simple `Dictionary<string, string>`. But when used, the file is deserialized in `TfsUserMappingTool`, but it tried to deserialize data as `List<IdentityMapData>`. I moved serialization and deserialization to `TfsUserMappingTool` as static method, so they are next to each other and both works with `Dictionary<string, string>`.
2 parents af5270e + 31e073d commit cd66dbd

File tree

2 files changed

+39
-26
lines changed

2 files changed

+39
-26
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,14 @@ protected override void InternalExecute()
6464

6565
List<IdentityMapData> usersToMap = data.IdentityMap.Where(x => x.Source.DisplayName != x.Target?.DisplayName).ToList();
6666
Log.LogInformation("Filtered to {usersToMap} total viable mappings", usersToMap.Count);
67-
Dictionary<string, string> usermappings = [];
67+
Dictionary<string, string> usermappings = new(StringComparer.CurrentCultureIgnoreCase);
6868
foreach (IdentityMapData userMapping in usersToMap)
6969
{
7070
// We cannot use ToDictionary(), because there can be multiple users with the same display name and so
7171
// it would throw with duplicate key. This way we just overwrite the value – last item in source wins.
7272
usermappings[userMapping.Source.DisplayName] = userMapping.Target?.DisplayName;
7373
}
74-
File.WriteAllText(CommonTools.UserMapping.Options.UserMappingFile, JsonConvert.SerializeObject(usermappings, Formatting.Indented));
75-
Log.LogInformation("User mappings writen to: {LocalExportJsonFile}", CommonTools.UserMapping.Options.UserMappingFile);
74+
TfsUserMappingTool.SerializeUserMap(CommonTools.UserMapping.Options.UserMappingFile, usermappings, Log);
7675
if (Options.ExportAllUsers)
7776
{
7877
ExportAllUsers(data);

src/MigrationTools.Clients.TfsObjectModel/Tools/TfsUserMappingTool.cs

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using Microsoft.Extensions.Logging;
56
using Microsoft.Extensions.Options;
@@ -8,6 +9,7 @@
89
using MigrationTools.DataContracts;
910
using MigrationTools.Processors.Infrastructure;
1011
using MigrationTools.Tools.Infrastructure;
12+
using Newtonsoft.Json;
1113
using Riok.Mapperly.Abstractions;
1214

1315
namespace MigrationTools.Tools
@@ -19,13 +21,40 @@ public class TfsUserMappingTool : Tool<TfsUserMappingToolOptions>
1921
{
2022
new public TfsUserMappingToolOptions Options => (TfsUserMappingToolOptions)base.Options;
2123

22-
public TfsUserMappingTool(IOptions<TfsUserMappingToolOptions> options, IServiceProvider services, ILogger<TfsUserMappingTool> logger, ITelemetryLogger telemetryLogger) : base(options, services, logger, telemetryLogger)
24+
public TfsUserMappingTool(
25+
IOptions<TfsUserMappingToolOptions> options,
26+
IServiceProvider services,
27+
ILogger<TfsUserMappingTool> logger,
28+
ITelemetryLogger telemetryLogger)
29+
: base(options, services, logger, telemetryLogger)
2330
{
31+
UserMappings = new Lazy<Dictionary<string, string>>(GetMappingFileData);
2432
}
2533

2634
private readonly CaseInsensitiveStringComparer _workItemNameComparer = new();
2735
private readonly TfsUserMappingToolMapper _mapper = new();
2836

37+
public static void SerializeUserMap(string fileName, Dictionary<string, string> userMap, ILogger logger)
38+
{
39+
File.WriteAllText(fileName, JsonConvert.SerializeObject(userMap, Formatting.Indented));
40+
logger.LogInformation("User mappings writen to: {fileName}", fileName);
41+
}
42+
43+
public static Dictionary<string, string> DeserializeUserMap(string fileName, ILogger logger)
44+
{
45+
try
46+
{
47+
string fileData = File.ReadAllText(fileName);
48+
var mapping = JsonConvert.DeserializeObject<Dictionary<string, string>>(fileData);
49+
return new Dictionary<string, string>(mapping, StringComparer.CurrentCultureIgnoreCase);
50+
}
51+
catch (Exception)
52+
{
53+
logger.LogError($"TfsUserMappingTool::DeserializeUserMap User mapping could not be deserialized from file '{fileName}'.", fileName);
54+
}
55+
return [];
56+
}
57+
2958
private HashSet<string> GetUsersFromWorkItems(List<WorkItemData> workitems, List<string> identityFieldsToCheck)
3059
{
3160
HashSet<string> foundUsers = new(StringComparer.CurrentCultureIgnoreCase);
@@ -53,40 +82,25 @@ public void MapUserIdentityField(Field field)
5382
if (Options.Enabled && Options.IdentityFieldsToCheck.Contains(field.ReferenceName))
5483
{
5584
Log.LogDebug($"TfsUserMappingTool::MapUserIdentityField [ReferenceName|{field.ReferenceName}]");
56-
var mapps = GetMappingFileData();
57-
if (mapps != null && mapps.ContainsKey(field.Value.ToString()))
85+
if (UserMappings.Value.ContainsKey(field.Value.ToString()))
5886
{
5987
var original = field.Value;
60-
field.Value = mapps[field.Value.ToString()];
88+
field.Value = UserMappings.Value[field.Value.ToString()];
6189
Log.LogDebug($"TfsUserMappingTool::MapUserIdentityField::Map:[original|{original}][new|{field.Value}]");
6290
}
6391
}
6492
}
6593

66-
private Dictionary<string, string> _UserMappings = null;
94+
public Lazy<Dictionary<string, string>> UserMappings { get; }
6795

6896
private Dictionary<string, string> GetMappingFileData()
6997
{
70-
if (!System.IO.File.Exists(Options.UserMappingFile))
98+
if (!File.Exists(Options.UserMappingFile))
7199
{
72100
Log.LogError("TfsUserMappingTool::GetMappingFileData:: The UserMappingFile '{UserMappingFile}' cant be found! Provide a valid file or disable TfsUserMappingTool!", Options.UserMappingFile);
73-
_UserMappings = new Dictionary<string, string>();
74-
}
75-
if (_UserMappings == null)
76-
{
77-
var fileData = System.IO.File.ReadAllText(Options.UserMappingFile);
78-
try
79-
{
80-
var fileMaps = Newtonsoft.Json.JsonConvert.DeserializeObject<List<IdentityMapData>>(fileData);
81-
_UserMappings = fileMaps.ToDictionary(x => x.Source.DisplayName, x => x.Target?.DisplayName);
82-
}
83-
catch (Exception)
84-
{
85-
_UserMappings = new Dictionary<string, string>();
86-
Log.LogError($"TfsUserMappingTool::GetMappingFileData [UserMappingFile|{Options.UserMappingFile}] <-- invalid - No mapping are applied!");
87-
}
101+
return [];
88102
}
89-
return _UserMappings;
103+
return DeserializeUserMap(Options.UserMappingFile, Log);
90104
}
91105

92106
private List<IdentityItemData> GetUsersListFromServer(IGroupSecurityService gss)

0 commit comments

Comments
 (0)