Skip to content
Merged
4 changes: 2 additions & 2 deletions Source/TeamFoundation.WebApi/HyperlinkFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ public Uri GetWorkItemUrl(int id, bool fullScreen = true)
return builder.Uri;
}

public Uri GetPullRequestUrl(int id, string repositoryName)
public Uri GetPullRequestUrl(int id, string projectName, string repositoryName)
{
UriBuilder builder = new UriBuilder(this.BaseUrl);
builder.Path = CombinePath(builder.Path, ProjectName, "_git", repositoryName, "pullrequest", id.ToString());
builder.Path = CombinePath(builder.Path, projectName, "_git", repositoryName, "pullrequest", id.ToString());
return builder.Uri;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/TeamMate/Controls/GlobalCommandBar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
Visibility="{Binding ElementName=self,
Path=Type,
Converter={x:Static fw:Converters.Visibility},
ConverterParameter={x:Static vm:CommandBarType.CodeReviews}}">
ConverterParameter={x:Static vm:CommandBarType.PullRequests}}">
<Button Command="{x:Static tmr:TeamMateCommands.Refresh}" Style="{StaticResource CommandAppBarButtonStyle}" />
<Button Command="{x:Static tmr:TeamMateCommands.MarkAllAsRead}" Style="{StaticResource CommandAppBarButtonStyle}" />
</StackPanel>
Expand Down
6 changes: 6 additions & 0 deletions Source/TeamMate/Model/ProjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Services.Graph.Client;

namespace Microsoft.Tools.TeamMate.Model
{
Expand Down Expand Up @@ -36,6 +38,10 @@ public ProjectContext(ProjectReference reference)

public WorkItemTrackingBatchHttpClient WorkItemTrackingBatchClient { get; set; }

public GraphHttpClient GraphClient { get; set; }

public Task<List<GraphUser>> UsersAsync { get; set; }

public string ProjectName { get; set; }

public ICollection<WorkItemTypeInfo> WorkItemTypes { get; set; }
Expand Down
17 changes: 13 additions & 4 deletions Source/TeamMate/Model/ProjectContextSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ private PullRequestQueryInfo ReadPullRequestQueryTileInfo(XElement e)

query.Name = e.GetAttribute<string>(Schema.Name);
query.ReviewStatus = e.GetAttribute<PullRequestQueryReviewStatus>(Schema.ReviewStatus);
query.AssignedTo = e.GetAttribute<string>(Schema.AssignedTo);
query.CreatedBy = e.GetAttribute<string>(Schema.CreatedBy);
query.AssignedTo = e.GetAttribute<Guid?>(Schema.AssignedTo);
query.CreatedBy = e.GetAttribute<Guid?>(Schema.CreatedBy);
query.Project = e.GetAttribute<string>(Schema.PullRequestProject);
query.UIAssignedTo = e.GetAttribute<string>(Schema.UIAssignedTo);
query.UICreatedBy = e.GetAttribute<string>(Schema.UICreatedBy);

return query;
}
Expand Down Expand Up @@ -143,8 +146,11 @@ private XElement WritePullRequestQueryTileInfo(PullRequestQueryInfo query)
XElement e = new XElement(Schema.PullRequestQueryInfo);
e.SetAttribute<string>(Schema.Name, query.Name);
e.SetAttribute<PullRequestQueryReviewStatus>(Schema.ReviewStatus, query.ReviewStatus);
e.SetAttribute<string>(Schema.CreatedBy, query.CreatedBy);
e.SetAttribute<string>(Schema.AssignedTo, query.AssignedTo);
e.SetAttribute<Guid?>(Schema.CreatedBy, query.CreatedBy);
e.SetAttribute<Guid?>(Schema.AssignedTo, query.AssignedTo);
e.SetAttribute<string>(Schema.UICreatedBy, query.UICreatedBy);
e.SetAttribute<string>(Schema.UIAssignedTo, query.UIAssignedTo);
e.SetAttribute<string>(Schema.PullRequestProject, query.Project);

return e;
}
Expand Down Expand Up @@ -385,6 +391,8 @@ private static class Schema
public const string State = "State";
public const string Title = "Title";
public const string Revision = "Revision";
public const string UIAssignedTo = "UIAssignedTo";
public const string UICreatedBy = "UICreatedBy";

public static XName RecentItems = "RecentItems";
public static XName RecentlyViewedWorkItems = "RecentlyViewedWorkItems";
Expand Down Expand Up @@ -421,6 +429,7 @@ private static class Schema
public static readonly XName PullRequest = "PullRequest";
public const string PullRequestId = "PullRequestId";
public const string PullRequestProjectId = "PullRequestProjectId";
public const string PullRequestProject = "PullRequestProject";

// ProjectSettings Stuff
public static readonly XName ProjectSettings = "ProjectSettings";
Expand Down
10 changes: 8 additions & 2 deletions Source/TeamMate/Model/PullRequestQueryInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ public class PullRequestQueryInfo

public string Name { get; set; }
public PullRequestQueryReviewStatus ReviewStatus { get; set; }
public string CreatedBy { get; set; }
public Guid? CreatedBy { get; set; }

public string AssignedTo { get; set; }
public Guid? AssignedTo { get; set; }

public string UICreatedBy { get; set; }

public string UIAssignedTo { get; set; }

public string Project { get; set; }
}
public enum PullRequestQueryReviewStatus
{
Expand Down
118 changes: 118 additions & 0 deletions Source/TeamMate/Services/ResolverService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using Microsoft.VisualStudio.Services.Graph.Client;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace Microsoft.Tools.TeamMate.Services
{
public class ResolverService
{
private List<GraphUser> GraphUserCache { get; set; }

private List<GraphGroup> GraphGroupCache { get; set; }

private List<Task> Tasks = new List<Task>();

private async Task<List<GraphUser>> FetchUsersAsync(
GraphHttpClient graphClient)
{
if (GraphUserCache != null)
{
return GraphUserCache;
}

List<GraphUser> users = new List<GraphUser>();

string continuationToken = null;
do
{
var data = await graphClient.ListUsersAsync(null, continuationToken);
continuationToken = data.ContinuationToken != null ? data.ContinuationToken.First() : null;
foreach (var user in data.GraphUsers)
{
users.Add(user);
}
}
while (continuationToken != null);

GraphUserCache = users;

return users;
}

private async Task<List<GraphGroup>> FetchGroupsAsync(
GraphHttpClient graphClient)
{
if (GraphGroupCache != null)
{
return GraphGroupCache;
}

List<GraphGroup> groups = new List<GraphGroup>();

string continuationToken = null;
do
{
// ListGroupsAsync
var data = await graphClient.ListGroupsAsync(null, null, continuationToken);
continuationToken = data.ContinuationToken != null ? data.ContinuationToken.First() : null;
foreach (var group in data.GraphGroups)
{
groups.Add(group);
}
}
while (continuationToken != null);

GraphGroupCache = groups;

return groups;
}

public void FetchDataSync(
GraphHttpClient client)
{
Tasks.Add(FetchUsersAsync(client));
Tasks.Add(FetchGroupsAsync(client));
}

public async Task<Guid?> Resolve(
GraphHttpClient client,
string value)
{
if (value == null)
{
return null;
}

await Task.Run(() => { foreach (var task in this.Tasks) { task.Wait(); } });
Comment thread
supermem613 marked this conversation as resolved.

foreach (var user in GraphUserCache)
{
if (user.MailAddress != null &&
(user.MailAddress.Contains(value)))
{
var storageKey = client.GetStorageKeyAsync(user.Descriptor).Result;

return storageKey.Value;
}
}

foreach (var group in GraphGroupCache)
{
if (group.MailAddress != null &&
(group.MailAddress.Contains(value)))
{
var storageKey = client.GetStorageKeyAsync(group.Descriptor).Result;

return storageKey.Value;
}
}

throw new ArgumentException("Could not resolve '" + value + "'. Try the full email for the person and/or group.");

}
}
}
11 changes: 10 additions & 1 deletion Source/TeamMate/Services/VstsConnectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using ProjectHttpClient = Microsoft.TeamFoundation.Core.WebApi.ProjectHttpClient;
using WorkItemField = Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemField;
using WorkItemType = Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemType;
using Microsoft.VisualStudio.Services.Graph.Client;

namespace Microsoft.Tools.TeamMate.Services
{
Expand All @@ -35,6 +36,10 @@ public class VstsConnectionService

[Import]
public WindowService WindowService { get; set; }

[Import]
public ResolverService ResolverService { get; set; }


private async Task<VssConnection> ConnectAsync(Uri projectCollectionUri, CancellationToken cancellationToken = default(CancellationToken))
{
Expand Down Expand Up @@ -137,6 +142,7 @@ private async Task<ProjectContext> DoConnectAsync(ProjectInfo projectInfo, Cance
WorkItemTrackingBatchHttpClient batchWitClient = connection.GetClient<WorkItemTrackingBatchHttpClient>();
ProjectHttpClient projectClient = connection.GetClient<ProjectHttpClient>();
GitHttpClient gitClient = connection.GetClient<GitHttpClient>();
GraphHttpClient graphClient = connection.GetClient<GraphHttpClient>();

var projectId = projectInfo.Reference.ProjectId;

Expand Down Expand Up @@ -174,6 +180,7 @@ private async Task<ProjectContext> DoConnectAsync(ProjectInfo projectInfo, Cance
projectContext.WorkItemTrackingClient = witClient;
projectContext.WorkItemTrackingBatchClient = batchWitClient;
projectContext.GitHttpClient = gitClient;
projectContext.GraphClient = graphClient;
projectContext.WorkItemTypes = workItemTypeInfos;
projectContext.ProjectInfo = projectInfo;
projectContext.Identity = identity;
Expand All @@ -183,6 +190,9 @@ private async Task<ProjectContext> DoConnectAsync(ProjectInfo projectInfo, Cance
projectContext.WorkItemFieldsByName = fields.ToDictionary(f => f.ReferenceName, StringComparer.OrdinalIgnoreCase);
projectContext.RequiredWorkItemFieldNames = GetWorkItemFieldsToPrefetch(projectContext.WorkItemFieldsByName);

this.ResolverService.FetchDataSync(
graphClient);

return projectContext;
}
catch (Exception e)
Expand All @@ -207,7 +217,6 @@ private async Task<ProjectContext> DoConnectAsync(ProjectInfo projectInfo, Cance

return null;
}

public ICollection<string> GetWorkItemFieldsToPrefetch(IDictionary<string, WorkItemField> availableFields)
{
// Prefetch the fields that are interesting to our services or object model...
Expand Down
4 changes: 2 additions & 2 deletions Source/TeamMate/Services/WindowService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Tools.TeamMate.Foundation.Shell;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.Tools.TeamMate.Foundation.Shell;
using Microsoft.Tools.TeamMate.Foundation.Threading;
using Microsoft.Tools.TeamMate.Foundation.Windows;
using Microsoft.Tools.TeamMate.Foundation.Windows.Controls;
Expand Down Expand Up @@ -381,7 +382,6 @@ private void HandleQuickSearchTriggered(object sender, QuickSearchTriggeredEvent
this.MainWindowViewModel.Search(e.SearchText, true, true);
}


public bool RequestShutdown()
{
bool shouldCancel = this.PromptSaveOnDirtyWindows();
Expand Down
1 change: 1 addition & 0 deletions Source/TeamMate/TeamMate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
<Compile Include="Services\AsyncWriterService.cs" />
<Compile Include="Services\ExternalWebBrowserService.cs" />
<Compile Include="Services\GlobalCommandService.cs" />
<Compile Include="Services\ResolverService.cs" />
<Compile Include="Services\HistoryService.cs" />
<Compile Include="Services\SearchService.cs" />
<Compile Include="Services\SessionService.cs" />
Expand Down
2 changes: 1 addition & 1 deletion Source/TeamMate/ViewModels/PageViewModelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum CommandBarType
None,
Home,
WorkItems,
CodeReviews,
PullRequests,
Projects
}
}
1 change: 0 additions & 1 deletion Source/TeamMate/ViewModels/ProjectPickerDialogViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ public ICollection<TeamProjectReference> SelectedProjects
get { return this.selectedProjects; }
set { this.SetProperty(ref this.selectedProjects, value); }
}

public void CancelConnect()
{
this.previousCancellationTokenSource.Cancel();
Expand Down
4 changes: 2 additions & 2 deletions Source/TeamMate/ViewModels/PullRequestPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class PullRequestPageViewModel : PageViewModelBase, ICommandProvider, IFi

public PullRequestPageViewModel()
{
this.CommandBarType = CommandBarType.CodeReviews;
this.CommandBarType = CommandBarType.PullRequests;
this.modelList = new List<PullRequestRowViewModel>();
this.collectionView = new ListCollectionView(this.modelList);

Expand Down Expand Up @@ -63,7 +63,7 @@ private ListViewModel CreateListViewModel(ICollectionView collectionView)
model.Filters.Add(new ListViewFilter("Pending", (o) => ((PullRequestRowViewModel)o).IsPending));
model.Filters.Add(new ListViewFilter("Waiting", (o) => ((PullRequestRowViewModel)o).IsWaiting));
model.Filters.Add(new ListViewFilter("Signed Off", (o) => ((PullRequestRowViewModel)o).IsSignedOff));
model.Filters.Add(new ListViewFilter("Not Signed Off By Me", (o) => !((PullRequestRowViewModel)o).IsSignedOffByMe));
model.Filters.Add(new ListViewFilter("Not Signed Off / Declined By Me", (o) => !((PullRequestRowViewModel)o).IsSignedOffOrDeclinedByMe));
Comment thread
supermem613 marked this conversation as resolved.
model.Filters.Add(new ListViewFilter("Completed", (o) => ((PullRequestRowViewModel)o).IsCompleted));

model.Fields.Add(ListFieldInfo.Create<string>("CreatedBy", "Created By"));
Expand Down
Loading