Skip to content

Commit b6b74d4

Browse files
author
Andrew Kanieski
committed
Various bug fixes
1 parent 60098d9 commit b6b74d4

File tree

10 files changed

+158
-33
lines changed

10 files changed

+158
-33
lines changed

AzureDevOpsMigrator.WPF/Pages/EndpointPage.xaml.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ private void ResetTestOn_TextChanged(object sender, TextChangedEventArgs e)
145145

146146
private void Button_GeneratePat_Click(object sender, RoutedEventArgs e)
147147
{
148-
System.Diagnostics.Process.Start($"{Model.EndpointUri}/_usersSettings/tokens");
148+
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd", $"/C start {Model.EndpointUri}/_usersSettings/tokens")
149+
{
150+
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
151+
CreateNoWindow = true,
152+
UseShellExecute = true
153+
});
149154
}
150155

151156
private void Button_LoadProjects_Click(object sender, RoutedEventArgs e)

AzureDevOpsMigrator.WPF/Pages/RunMigrationPage.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private void Run()
112112
}
113113
catch (Exception ex)
114114
{
115+
GUILogger_Logged(this, new LogEventArgs(ex.ToString(), Microsoft.Extensions.Logging.LogLevel.Error));
115116
IsRunning = false;
116117
RefreshBindings();
117118
await Task.Delay(500);

AzureDevOpsMigrator.WPF/Pages/WitPages/WitQueryPage.xaml

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,63 @@
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
55
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
66
xmlns:local="clr-namespace:AzureDevOpsMigrator.WPF.Pages.WitPages"
7+
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
78
xmlns:clr="clr-namespace:System;assembly=mscorlib"
89
mc:Ignorable="d"
9-
d:DesignHeight="784" d:DesignWidth="1152"
10+
d:DesignHeight="784" d:DesignWidth="1152" Focusable="False"
1011
Title="WitQueryPage">
1112
<Page.Resources>
1213
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
1314
<clr:String x:Key="Summary">Found {0} work items.. showing top 100</clr:String>
1415
</Page.Resources>
1516

16-
<Grid>
17+
<Grid Focusable="False">
1718
<Grid.RowDefinitions>
1819
<RowDefinition Height="70"></RowDefinition>
1920
<RowDefinition Height="*"></RowDefinition>
2021
<RowDefinition Height="40"></RowDefinition>
2122
</Grid.RowDefinitions>
22-
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,20">
23-
<TextBlock VerticalAlignment="Center">Query Filter</TextBlock>
24-
<TextBox Width="500" Margin="20,0,20,0" Text="{Binding Model.SourceQuery}"></TextBox>
25-
<Button Name="Button_Load" Height="40" Click="Button_Load_Click">Load Preview</Button>
26-
</StackPanel>
23+
<Grid Grid.Row="0" Margin="0,0,0,20">
24+
<Grid.ColumnDefinitions>
25+
<ColumnDefinition Width="100"></ColumnDefinition>
26+
<ColumnDefinition Width="*"></ColumnDefinition>
27+
<ColumnDefinition Width="50"></ColumnDefinition>
28+
<ColumnDefinition Width="150"></ColumnDefinition>
29+
</Grid.ColumnDefinitions>
30+
<TextBlock Grid.Column="0" VerticalAlignment="Center">Query Filter</TextBlock>
31+
<TextBox Grid.Column="1" Text="{Binding Model.SourceQuery}" Margin="0,0,10,0"></TextBox>
32+
<Button Grid.Column="2" Name="Button_Help" Height="40" Width="40" Margin="0,0,10,0" Click="Button_Help_Click">
33+
<fa:IconBlock Icon="QuestionCircle" FontSize="18"></fa:IconBlock>
34+
</Button>
35+
<Button Grid.Column="3" Name="Button_Load" Height="40" Click="Button_Load_Click">Load Preview</Button>
36+
</Grid>
2737
<DataGrid Grid.Row="1" ItemsSource="{Binding Results}" AutoGenerateColumns="True" Name="dgItems">
2838
</DataGrid>
2939
<TextBlock HorizontalAlignment="Center" Margin="0,40,0,0" FontStyle="Italic" Grid.Row="1" Text="No results found" Visibility="{Binding Items.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=dgItems}" />
3040
<TextBlock Grid.Row="2" Margin="0,10,0,0" HorizontalAlignment="Center" Text="{Binding Total, StringFormat={StaticResource Summary}}"
3141
Visibility="{Binding HasRecords, Converter={StaticResource BooleanToVisibilityConverter}}"/>
42+
43+
<Grid Grid.Row="1" Margin="0" Background="DarkSlateGray" Opacity="0.5" Visibility="{Binding FieldPopupVisible, Converter={StaticResource BooleanToVisibilityConverter}}"></Grid>
44+
<Border Grid.Row="1" Margin="25" BorderBrush="{StaticResource PanelBorder}" Background="White" Visibility="{Binding FieldPopupVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
45+
<Grid>
46+
<Grid.RowDefinitions>
47+
<RowDefinition Height="40"></RowDefinition>
48+
<RowDefinition Height="*"></RowDefinition>
49+
</Grid.RowDefinitions>
50+
<Border BorderThickness="0,0,0,1" BorderBrush="DarkGray" >
51+
<Grid Height="40" Background="{StaticResource PanelBackground}">
52+
<Grid.ColumnDefinitions>
53+
<ColumnDefinition Width="*"></ColumnDefinition>
54+
<ColumnDefinition Width="40"></ColumnDefinition>
55+
</Grid.ColumnDefinitions>
56+
<TextBlock Style="{StaticResource AccordionHeader}" VerticalAlignment="Center" Margin="10,0,0,0">Available Fields</TextBlock>
57+
<Button Grid.Column="1" BorderThickness="0" Background="Transparent" Click="Button_Help_Click">
58+
<fa:IconBlock Icon="WindowClose" FontSize="20" Foreground="{ StaticResource DarkColor}"/>
59+
</Button>
60+
</Grid>
61+
</Border>
62+
<DataGrid ItemsSource="{Binding ProjectFields}" Margin="15" VerticalScrollBarVisibility="Visible" Grid.Row="1" CanUserAddRows="False" CanUserDeleteRows="False"/>
63+
</Grid>
64+
</Border>
3265
</Grid>
3366
</Page>

AzureDevOpsMigrator.WPF/Pages/WitPages/WitQueryPage.xaml.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,22 @@ public WorkItemSummary(WorkItem wit)
4444
/// </summary>
4545
public partial class WitQueryPage : Page, INotifyPropertyChanged
4646
{
47+
public List<WorkItemField> ProjectFields { get; set; }
4748
private IEndpointService _endpoint;
4849

4950
public event PropertyChangedEventHandler PropertyChanged;
5051

5152
public MigrationConfig Model => MainWindow.CurrentModel.CurrentConfig;
53+
private bool _fieldPopupVisible { get; set; }
54+
public bool FieldPopupVisible
55+
{
56+
get => _fieldPopupVisible;
57+
set
58+
{
59+
_fieldPopupVisible = value;
60+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FieldPopupVisible)));
61+
}
62+
}
5263
public ObservableCollection<WorkItemSummary> Results { get; set; } = new ObservableCollection<WorkItemSummary>();
5364
public int Total { get; set; }
5465
public bool HasRecords { get; set; }
@@ -71,8 +82,9 @@ private async Task Load()
7182
_endpoint.Initialize(Model.SourceEndpointConfig.EndpointUri, Model.SourceEndpointConfig.PersonalAccessToken);
7283
try
7384
{
85+
ProjectFields = (await _endpoint.GetFields(Model.SourceEndpointConfig.ProjectName, CancellationToken.None)).ToList();
7486
var result = await _endpoint.GetWorkItemsAsync(
75-
$"select * from WorkItems {(string.IsNullOrEmpty(Model.SourceQuery) ? "" : "where " + Model.SourceQuery)}",
87+
$"select * from WorkItems where [System.TeamProject] = '{Model.SourceEndpointConfig.ProjectName}' {(string.IsNullOrEmpty(Model.SourceQuery) ? "" : $"and ({Model.SourceQuery})")}",
7688
CancellationToken.None,
7789
top: 100);
7890
Total = result.TotalCount;
@@ -81,12 +93,17 @@ private async Task Load()
8193
HasRecords = Total > 0;
8294
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Total)));
8395
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasRecords)));
96+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProjectFields)));
8497
}
8598
catch (Exception ex)
8699
{
87100
MessageBox.Show(ex.Message);
88101
}
89102
}
90103

104+
private void Button_Help_Click(object sender, RoutedEventArgs e)
105+
{
106+
FieldPopupVisible = !FieldPopupVisible;
107+
}
91108
}
92109
}

AzureDevOpsMigrator.WPF/Pages/WorkItemsPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<TextBlock PreviewMouseDown="Tab_Selected" Name="IterationsTab" Style="{StaticResource CustomTab}">Iterations</TextBlock>
2626
<TextBlock PreviewMouseDown="Tab_Selected" Name="TransformationsTab" Style="{StaticResource CustomTab}">Transformation</TextBlock>
2727
</StackPanel>
28-
<Frame Name="WitPage" Grid.Row="2">
28+
<Frame Name="WitPage" Grid.Row="2" Focusable="False">
2929

3030
</Frame>
3131
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">

AzureDevOpsMigrator/Models/Migration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class MigrationConfig
1313
public EndpointConfig TargetEndpointConfig { get; set; }
1414

1515
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, ItemTypeNameHandling = TypeNameHandling.Objects, TypeNameHandling = TypeNameHandling.Objects)]
16-
public ObservableCollection<object> Transformations { get; set; }
16+
public ObservableCollection<object> Transformations { get; set; } = new ObservableCollection<object>();
1717
public string SourceQuery { get; set; } = "";
1818
public int MaxDegreeOfParallelism { get; set; } = 3;
1919
public bool FixHyperlinks { get; set; } = true;

AzureDevOpsMigrator/Services/Endpoints/RestEndpointService.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ public async Task<IEnumerable<GitCommitRef>> GetCommitRefs(Guid repoId, Cancella
117117

118118
public async Task UpsertClassificationNodesAsync(WorkItemClassificationNode node, string projectName, TreeStructureGroup group, CancellationToken token)
119119
{
120-
await _witClient.CreateOrUpdateClassificationNodeAsync(node, projectName, group, cancellationToken: token);
120+
var path = string.Join("/", node.Path.Split('\\').Skip(3).Where(x => x != node.Name));
121+
await _witClient.CreateOrUpdateClassificationNodeAsync(node, projectName, group, path: path, cancellationToken: token);
121122
}
122123

123124
public async Task<IEnumerable<WorkItemClassificationNode>> GetAreaPaths(string projectName, CancellationToken token) =>
@@ -143,11 +144,11 @@ public async Task<IEnumerable<TeamProjectReference>> GetAllProjects()
143144
return await _projectsClient.GetProjects(top: ushort.MaxValue);
144145
}
145146

146-
public async Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token)
147+
public async Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token, string projectName = "")
147148
{
148149
CheckInitialized();
149150
var wiql = new Wiql();
150-
wiql.Query = $"SELECT * FROM WORKITEMS WHERE {wiqlQuery}";
151+
wiql.Query = $"SELECT * FROM WORKITEMS where [System.TeamProject] = '{projectName}' {(string.IsNullOrEmpty(wiqlQuery) ? "" : $" and ({wiqlQuery})")}";
151152
return (await _witClient.QueryByWiqlAsync(wiql, false, cancellationToken: token)).WorkItems.Select(i => i.Id);
152153
}
153154

@@ -281,7 +282,7 @@ public async Task<WorkItemField> GetField(string referenceName, CancellationToke
281282

282283
public async Task<WorkItem> GetWorkItemByMigrationState(string projectName, string migrationStateField, string url, CancellationToken token)
283284
{
284-
var existing = await GetIdsByWiql($"[Custom.{migrationStateField}] = '{url}'", token);
285+
var existing = await GetIdsByWiql($"[Custom.{migrationStateField}] = '{url}'", token, projectName);
285286

286287
if (existing.Count() == 0)
287288
{
@@ -326,7 +327,7 @@ public interface IEndpointService
326327
Task<IEnumerable<WorkItemClassificationNode>> GetClassificationNodes(string projectName, TreeStructureGroup type, CancellationToken token);
327328
Task<IEnumerable<WorkItemField>> GetFields(string project, CancellationToken token);
328329
Task<WorkItemField> CreateField(WorkItemField field, CancellationToken token);
329-
Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token);
330+
Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token, string projectName = "");
330331
Task<IEnumerable<WorkItemClassificationNode>> GetIterations(string projectName, CancellationToken token);
331332
Task<TeamProject> GetProject(string projectName);
332333
Task<WorkItem> GetWorkItem(string projectName, int workItemId, CancellationToken token, WorkItemExpand expand = WorkItemExpand.None);

AzureDevOpsMigrator/Services/Migrators/ClassificationNodeMigrator.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ public async Task ExecuteAsync(System.Threading.CancellationToken token)
6262
if (existing == null)
6363
{
6464
// Create a new area path in target
65+
var updatedPath = @"\" + _config.TargetEndpointConfig.ProjectName + (_nodeType == TreeNodeStructureType.Area ? @"\Area\" : @"\Iteration\") + sourceNode.Path;
6566
upsertedNodes.Add((SyncState.Create, new WorkItemClassificationNode()
6667
{
67-
Path = @"\" + _config.TargetEndpointConfig.ProjectName + (_nodeType == TreeNodeStructureType.Area ? @"\Area\" : @"\Iteration\") + sourceNode.Path,
68+
Path = updatedPath,
6869
Name = sourceNode.Path.Split(@"\").Last(),
6970
StructureType = sourceNode.Node.StructureType,
70-
Attributes = sourceNode.Node.Attributes
71+
Attributes = sourceNode.Node.Attributes,
72+
HasChildren = sourceNode.Node.HasChildren
7173
}));
7274
}
7375
else
@@ -78,15 +80,18 @@ public async Task ExecuteAsync(System.Threading.CancellationToken token)
7880
{
7981
foreach (var sourcePair in sourceNode.Node.Attributes)
8082
{
81-
if (existing.Node.Attributes.ContainsKey(sourcePair.Key) && !existing.Node.Attributes[sourcePair.Key].Equals(sourcePair.Value))
83+
if (existing.Node.Attributes != null)
8284
{
83-
changes = true;
84-
existing.Node.Attributes[sourcePair.Key] = sourcePair.Value;
85-
}
86-
else if (!existing.Node.Attributes.ContainsKey(sourcePair.Key))
87-
{
88-
changes = true;
89-
existing.Node.Attributes.Add(sourcePair);
85+
if (existing.Node.Attributes.ContainsKey(sourcePair.Key) && !existing.Node.Attributes[sourcePair.Key].Equals(sourcePair.Value))
86+
{
87+
changes = true;
88+
existing.Node.Attributes[sourcePair.Key] = sourcePair.Value;
89+
}
90+
else if (!existing.Node.Attributes.ContainsKey(sourcePair.Key))
91+
{
92+
changes = true;
93+
existing.Node.Attributes.Add(sourcePair);
94+
}
9095
}
9196
}
9297
}

AzureDevOpsMigrator/Services/Migrators/OrchestratorService.cs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading;
1010
using System.Threading.Tasks;
1111
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
12+
using System.Collections.Generic;
1213

1314
namespace AzureDevOpsMigrator.Migrators
1415
{
@@ -74,10 +75,50 @@ public async Task<MigrationPlan> BuildMigrationPlan(CancellationToken token)
7475

7576
InitializeEndpoints();
7677

78+
var exceptions = new List<Exception>();
79+
7780
await Task.WhenAll(
78-
Task.Run(async () => plan.IterationsCount = await _iterationMigrator.GetPlannedCount(token)),
79-
Task.Run(async () => plan.AreaPathsCount = await _areaPathMigrator.GetPlannedCount(token)),
80-
Task.Run(async () => plan.WorkItemsCount = await _workItemMigrator.GetPlannedCount(token)));
81+
Task.Run(async () =>
82+
{
83+
try
84+
{
85+
return plan.IterationsCount = await _iterationMigrator.GetPlannedCount(token);
86+
}
87+
catch (Exception ex)
88+
{
89+
exceptions.Add(ex);
90+
return -1;
91+
}
92+
}),
93+
Task.Run(async () =>
94+
{
95+
try
96+
{
97+
return plan.AreaPathsCount = await _areaPathMigrator.GetPlannedCount(token);
98+
}
99+
catch (Exception ex)
100+
{
101+
exceptions.Add(ex);
102+
return -1;
103+
}
104+
}),
105+
Task.Run(async () =>
106+
{
107+
try
108+
{
109+
return plan.WorkItemsCount = await _workItemMigrator.GetPlannedCount(token);
110+
}
111+
catch (Exception ex)
112+
{
113+
exceptions.Add(ex);
114+
return -1;
115+
}
116+
}));
117+
118+
if (exceptions.Count > 0)
119+
{
120+
throw new MigrationException($"Failed to generate migration plan.", exceptions.First());
121+
}
81122

82123
return plan;
83124
}
@@ -129,9 +170,23 @@ public async Task ExecuteAsync(MigrationPlan plan, CancellationToken token)
129170
_currentPlan = plan;
130171
_log.LogInformation("Starting migrations");
131172

132-
await _areaPathMigrator.ExecuteAsync(token);
173+
if (_config.Execution.AreaPathMigratorEnabled)
174+
{
175+
await _areaPathMigrator.ExecuteAsync(token);
176+
}
177+
else
178+
{
179+
_log.LogInformation("Skipping area path migrations..");
180+
}
181+
if (_config.Execution.IterationsMigratorEnabled)
182+
{
183+
await _iterationMigrator.ExecuteAsync(token);
184+
}
185+
else
186+
{
187+
_log.LogInformation("Skipping iteration migrations..");
188+
}
133189

134-
await _iterationMigrator.ExecuteAsync(token);
135190

136191
await _workItemMigrator.ExecuteAsync(token);
137192
}

0 commit comments

Comments
 (0)