Skip to content

Commit

Permalink
Various bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Kanieski committed Dec 15, 2020
1 parent 60098d9 commit b6b74d4
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 33 deletions.
7 changes: 6 additions & 1 deletion AzureDevOpsMigrator.WPF/Pages/EndpointPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ private void ResetTestOn_TextChanged(object sender, TextChangedEventArgs e)

private void Button_GeneratePat_Click(object sender, RoutedEventArgs e)
{
System.Diagnostics.Process.Start($"{Model.EndpointUri}/_usersSettings/tokens");
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd", $"/C start {Model.EndpointUri}/_usersSettings/tokens")
{
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true
});
}

private void Button_LoadProjects_Click(object sender, RoutedEventArgs e)
Expand Down
1 change: 1 addition & 0 deletions AzureDevOpsMigrator.WPF/Pages/RunMigrationPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ private void Run()
}
catch (Exception ex)
{
GUILogger_Logged(this, new LogEventArgs(ex.ToString(), Microsoft.Extensions.Logging.LogLevel.Error));
IsRunning = false;
RefreshBindings();
await Task.Delay(500);
Expand Down
47 changes: 40 additions & 7 deletions AzureDevOpsMigrator.WPF/Pages/WitPages/WitQueryPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,63 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AzureDevOpsMigrator.WPF.Pages.WitPages"
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="784" d:DesignWidth="1152"
d:DesignHeight="784" d:DesignWidth="1152" Focusable="False"
Title="WitQueryPage">
<Page.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<clr:String x:Key="Summary">Found {0} work items.. showing top 100</clr:String>
</Page.Resources>

<Grid>
<Grid Focusable="False">
<Grid.RowDefinitions>
<RowDefinition Height="70"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,20">
<TextBlock VerticalAlignment="Center">Query Filter</TextBlock>
<TextBox Width="500" Margin="20,0,20,0" Text="{Binding Model.SourceQuery}"></TextBox>
<Button Name="Button_Load" Height="40" Click="Button_Load_Click">Load Preview</Button>
</StackPanel>
<Grid Grid.Row="0" Margin="0,0,0,20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center">Query Filter</TextBlock>
<TextBox Grid.Column="1" Text="{Binding Model.SourceQuery}" Margin="0,0,10,0"></TextBox>
<Button Grid.Column="2" Name="Button_Help" Height="40" Width="40" Margin="0,0,10,0" Click="Button_Help_Click">
<fa:IconBlock Icon="QuestionCircle" FontSize="18"></fa:IconBlock>
</Button>
<Button Grid.Column="3" Name="Button_Load" Height="40" Click="Button_Load_Click">Load Preview</Button>
</Grid>
<DataGrid Grid.Row="1" ItemsSource="{Binding Results}" AutoGenerateColumns="True" Name="dgItems">
</DataGrid>
<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}" />
<TextBlock Grid.Row="2" Margin="0,10,0,0" HorizontalAlignment="Center" Text="{Binding Total, StringFormat={StaticResource Summary}}"
Visibility="{Binding HasRecords, Converter={StaticResource BooleanToVisibilityConverter}}"/>

<Grid Grid.Row="1" Margin="0" Background="DarkSlateGray" Opacity="0.5" Visibility="{Binding FieldPopupVisible, Converter={StaticResource BooleanToVisibilityConverter}}"></Grid>
<Border Grid.Row="1" Margin="25" BorderBrush="{StaticResource PanelBorder}" Background="White" Visibility="{Binding FieldPopupVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border BorderThickness="0,0,0,1" BorderBrush="DarkGray" >
<Grid Height="40" Background="{StaticResource PanelBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource AccordionHeader}" VerticalAlignment="Center" Margin="10,0,0,0">Available Fields</TextBlock>
<Button Grid.Column="1" BorderThickness="0" Background="Transparent" Click="Button_Help_Click">
<fa:IconBlock Icon="WindowClose" FontSize="20" Foreground="{ StaticResource DarkColor}"/>
</Button>
</Grid>
</Border>
<DataGrid ItemsSource="{Binding ProjectFields}" Margin="15" VerticalScrollBarVisibility="Visible" Grid.Row="1" CanUserAddRows="False" CanUserDeleteRows="False"/>
</Grid>
</Border>
</Grid>
</Page>
19 changes: 18 additions & 1 deletion AzureDevOpsMigrator.WPF/Pages/WitPages/WitQueryPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,22 @@ public WorkItemSummary(WorkItem wit)
/// </summary>
public partial class WitQueryPage : Page, INotifyPropertyChanged
{
public List<WorkItemField> ProjectFields { get; set; }
private IEndpointService _endpoint;

public event PropertyChangedEventHandler PropertyChanged;

public MigrationConfig Model => MainWindow.CurrentModel.CurrentConfig;
private bool _fieldPopupVisible { get; set; }
public bool FieldPopupVisible
{
get => _fieldPopupVisible;
set
{
_fieldPopupVisible = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FieldPopupVisible)));
}
}
public ObservableCollection<WorkItemSummary> Results { get; set; } = new ObservableCollection<WorkItemSummary>();
public int Total { get; set; }
public bool HasRecords { get; set; }
Expand All @@ -71,8 +82,9 @@ private async Task Load()
_endpoint.Initialize(Model.SourceEndpointConfig.EndpointUri, Model.SourceEndpointConfig.PersonalAccessToken);
try
{
ProjectFields = (await _endpoint.GetFields(Model.SourceEndpointConfig.ProjectName, CancellationToken.None)).ToList();
var result = await _endpoint.GetWorkItemsAsync(
$"select * from WorkItems {(string.IsNullOrEmpty(Model.SourceQuery) ? "" : "where " + Model.SourceQuery)}",
$"select * from WorkItems where [System.TeamProject] = '{Model.SourceEndpointConfig.ProjectName}' {(string.IsNullOrEmpty(Model.SourceQuery) ? "" : $"and ({Model.SourceQuery})")}",
CancellationToken.None,
top: 100);
Total = result.TotalCount;
Expand All @@ -81,12 +93,17 @@ private async Task Load()
HasRecords = Total > 0;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Total)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasRecords)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProjectFields)));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

private void Button_Help_Click(object sender, RoutedEventArgs e)
{
FieldPopupVisible = !FieldPopupVisible;
}
}
}
2 changes: 1 addition & 1 deletion AzureDevOpsMigrator.WPF/Pages/WorkItemsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<TextBlock PreviewMouseDown="Tab_Selected" Name="IterationsTab" Style="{StaticResource CustomTab}">Iterations</TextBlock>
<TextBlock PreviewMouseDown="Tab_Selected" Name="TransformationsTab" Style="{StaticResource CustomTab}">Transformation</TextBlock>
</StackPanel>
<Frame Name="WitPage" Grid.Row="2">
<Frame Name="WitPage" Grid.Row="2" Focusable="False">

</Frame>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
Expand Down
2 changes: 1 addition & 1 deletion AzureDevOpsMigrator/Models/Migration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class MigrationConfig
public EndpointConfig TargetEndpointConfig { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore, ItemTypeNameHandling = TypeNameHandling.Objects, TypeNameHandling = TypeNameHandling.Objects)]
public ObservableCollection<object> Transformations { get; set; }
public ObservableCollection<object> Transformations { get; set; } = new ObservableCollection<object>();
public string SourceQuery { get; set; } = "";
public int MaxDegreeOfParallelism { get; set; } = 3;
public bool FixHyperlinks { get; set; } = true;
Expand Down
11 changes: 6 additions & 5 deletions AzureDevOpsMigrator/Services/Endpoints/RestEndpointService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ public async Task<IEnumerable<GitCommitRef>> GetCommitRefs(Guid repoId, Cancella

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

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

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

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

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

if (existing.Count() == 0)
{
Expand Down Expand Up @@ -326,7 +327,7 @@ public interface IEndpointService
Task<IEnumerable<WorkItemClassificationNode>> GetClassificationNodes(string projectName, TreeStructureGroup type, CancellationToken token);
Task<IEnumerable<WorkItemField>> GetFields(string project, CancellationToken token);
Task<WorkItemField> CreateField(WorkItemField field, CancellationToken token);
Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token);
Task<IEnumerable<int>> GetIdsByWiql(string wiqlQuery, CancellationToken token, string projectName = "");
Task<IEnumerable<WorkItemClassificationNode>> GetIterations(string projectName, CancellationToken token);
Task<TeamProject> GetProject(string projectName);
Task<WorkItem> GetWorkItem(string projectName, int workItemId, CancellationToken token, WorkItemExpand expand = WorkItemExpand.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ public async Task ExecuteAsync(System.Threading.CancellationToken token)
if (existing == null)
{
// Create a new area path in target
var updatedPath = @"\" + _config.TargetEndpointConfig.ProjectName + (_nodeType == TreeNodeStructureType.Area ? @"\Area\" : @"\Iteration\") + sourceNode.Path;
upsertedNodes.Add((SyncState.Create, new WorkItemClassificationNode()
{
Path = @"\" + _config.TargetEndpointConfig.ProjectName + (_nodeType == TreeNodeStructureType.Area ? @"\Area\" : @"\Iteration\") + sourceNode.Path,
Path = updatedPath,
Name = sourceNode.Path.Split(@"\").Last(),
StructureType = sourceNode.Node.StructureType,
Attributes = sourceNode.Node.Attributes
Attributes = sourceNode.Node.Attributes,
HasChildren = sourceNode.Node.HasChildren
}));
}
else
Expand All @@ -78,15 +80,18 @@ public async Task ExecuteAsync(System.Threading.CancellationToken token)
{
foreach (var sourcePair in sourceNode.Node.Attributes)
{
if (existing.Node.Attributes.ContainsKey(sourcePair.Key) && !existing.Node.Attributes[sourcePair.Key].Equals(sourcePair.Value))
if (existing.Node.Attributes != null)
{
changes = true;
existing.Node.Attributes[sourcePair.Key] = sourcePair.Value;
}
else if (!existing.Node.Attributes.ContainsKey(sourcePair.Key))
{
changes = true;
existing.Node.Attributes.Add(sourcePair);
if (existing.Node.Attributes.ContainsKey(sourcePair.Key) && !existing.Node.Attributes[sourcePair.Key].Equals(sourcePair.Value))
{
changes = true;
existing.Node.Attributes[sourcePair.Key] = sourcePair.Value;
}
else if (!existing.Node.Attributes.ContainsKey(sourcePair.Key))
{
changes = true;
existing.Node.Attributes.Add(sourcePair);
}
}
}
}
Expand Down
65 changes: 60 additions & 5 deletions AzureDevOpsMigrator/Services/Migrators/OrchestratorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using System.Collections.Generic;

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

InitializeEndpoints();

var exceptions = new List<Exception>();

await Task.WhenAll(
Task.Run(async () => plan.IterationsCount = await _iterationMigrator.GetPlannedCount(token)),
Task.Run(async () => plan.AreaPathsCount = await _areaPathMigrator.GetPlannedCount(token)),
Task.Run(async () => plan.WorkItemsCount = await _workItemMigrator.GetPlannedCount(token)));
Task.Run(async () =>
{
try
{
return plan.IterationsCount = await _iterationMigrator.GetPlannedCount(token);
}
catch (Exception ex)
{
exceptions.Add(ex);
return -1;
}
}),
Task.Run(async () =>
{
try
{
return plan.AreaPathsCount = await _areaPathMigrator.GetPlannedCount(token);
}
catch (Exception ex)
{
exceptions.Add(ex);
return -1;
}
}),
Task.Run(async () =>
{
try
{
return plan.WorkItemsCount = await _workItemMigrator.GetPlannedCount(token);
}
catch (Exception ex)
{
exceptions.Add(ex);
return -1;
}
}));

if (exceptions.Count > 0)
{
throw new MigrationException($"Failed to generate migration plan.", exceptions.First());
}

return plan;
}
Expand Down Expand Up @@ -129,9 +170,23 @@ public async Task ExecuteAsync(MigrationPlan plan, CancellationToken token)
_currentPlan = plan;
_log.LogInformation("Starting migrations");

await _areaPathMigrator.ExecuteAsync(token);
if (_config.Execution.AreaPathMigratorEnabled)
{
await _areaPathMigrator.ExecuteAsync(token);
}
else
{
_log.LogInformation("Skipping area path migrations..");
}
if (_config.Execution.IterationsMigratorEnabled)
{
await _iterationMigrator.ExecuteAsync(token);
}
else
{
_log.LogInformation("Skipping iteration migrations..");
}

await _iterationMigrator.ExecuteAsync(token);

await _workItemMigrator.ExecuteAsync(token);
}
Expand Down
Loading

0 comments on commit b6b74d4

Please sign in to comment.