Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit bff9aac

Browse files
authored
Merge pull request #1545 from github/feature/pr-user-reviews
Display Pull Request Reviews for a User.
2 parents 443ad2a + ca0a9d3 commit bff9aac

35 files changed

+1662
-3
lines changed

src/GitHub.App/Api/ApiClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public IObservable<User> GetUser()
8888
return gitHubClient.User.Current();
8989
}
9090

91+
public IObservable<User> GetUser(string login)
92+
{
93+
return gitHubClient.User.Get(login);
94+
}
95+
9196
public IObservable<Organization> GetOrganizations()
9297
{
9398
// Organization.GetAllForCurrent doesn't return all of the information we need (we

src/GitHub.App/GitHub.App.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@
212212
<Compile Include="Models\PullRequestDetailArgument.cs" />
213213
<Compile Include="Models\PullRequestReviewModel.cs" />
214214
<Compile Include="SampleData\PullRequestFilesViewModelDesigner.cs" />
215+
<Compile Include="SampleData\PullRequestReviewFileCommentViewModelDesigner.cs" />
216+
<Compile Include="SampleData\PullRequestReviewViewModelDesigner.cs" />
217+
<Compile Include="SampleData\PullRequestUserReviewsViewModelDesigner.cs" />
215218
<Compile Include="Services\EnterpriseCapabilitiesService.cs" />
216219
<Compile Include="Services\GlobalConnection.cs" />
217220
<Compile Include="Services\PullRequestEditorService.cs" />
@@ -240,7 +243,10 @@
240243
<Compile Include="ViewModels\GitHubPane\PullRequestCreationViewModel.cs" />
241244
<Compile Include="ViewModels\GitHubPane\NotAGitHubRepositoryViewModel.cs" />
242245
<Compile Include="ViewModels\GitHubPane\NotAGitRepositoryViewModel.cs" />
246+
<Compile Include="ViewModels\GitHubPane\PullRequestReviewFileCommentViewModel.cs" />
243247
<Compile Include="ViewModels\GitHubPane\PullRequestReviewSummaryViewModel.cs" />
248+
<Compile Include="ViewModels\GitHubPane\PullRequestReviewViewModel.cs" />
249+
<Compile Include="ViewModels\GitHubPane\PullRequestUserReviewsViewModel.cs" />
244250
<Compile Include="ViewModels\RepositoryFormViewModel.cs" />
245251
<Compile Include="ViewModels\TeamExplorer\RepositoryPublishViewModel.cs" />
246252
<Compile Include="Caches\CacheIndex.cs" />

src/GitHub.App/Models/PullRequestReviewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public class PullRequestReviewModel : IPullRequestReviewModel
1010
public string Body { get; set; }
1111
public PullRequestReviewState State { get; set; }
1212
public string CommitId { get; set; }
13+
public DateTimeOffset? SubmittedAt { get; set; }
1314
}
1415
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Reactive;
2+
using GitHub.ViewModels.GitHubPane;
3+
using ReactiveUI;
4+
5+
namespace GitHub.SampleData
6+
{
7+
public class PullRequestReviewFileCommentViewModelDesigner : IPullRequestReviewFileCommentViewModel
8+
{
9+
public string Body { get; set; }
10+
public string RelativePath { get; set; }
11+
public ReactiveCommand<Unit> Open { get; }
12+
}
13+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Threading.Tasks;
5+
using GitHub.Models;
6+
using GitHub.ViewModels.GitHubPane;
7+
using ReactiveUI;
8+
9+
namespace GitHub.SampleData
10+
{
11+
[ExcludeFromCodeCoverage]
12+
public class PullRequestReviewViewModelDesigner : PanePageViewModelBase, IPullRequestReviewViewModel
13+
{
14+
public PullRequestReviewViewModelDesigner()
15+
{
16+
PullRequestModel = new PullRequestModel(
17+
419,
18+
"Fix a ton of potential crashers, odd code and redundant calls in ModelService",
19+
new AccountDesigner { Login = "Haacked", IsUser = true },
20+
DateTimeOffset.Now - TimeSpan.FromDays(2));
21+
22+
Model = new PullRequestReviewModel
23+
{
24+
25+
SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1),
26+
User = new AccountDesigner { Login = "Haacked", IsUser = true },
27+
};
28+
29+
Body = @"Just a few comments. I don't feel too strongly about them though.
30+
31+
Otherwise, very nice work here! ✨";
32+
33+
StateDisplay = "approved";
34+
35+
FileComments = new[]
36+
{
37+
new PullRequestReviewFileCommentViewModelDesigner
38+
{
39+
Body = @"These should probably be properties. Most likely they should be readonly properties. I know that makes creating instances of these not look as nice as using property initializers when constructing an instance, but if these properties should never be mutated after construction, then it guides future consumers to the right behavior.
40+
41+
However, if you're two-way binding these properties to a UI, then ignore the readonly part and make them properties. But in that case they should probably be reactive properties (or implement INPC).",
42+
RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs",
43+
},
44+
new PullRequestReviewFileCommentViewModelDesigner
45+
{
46+
Body = "While I have no problems with naming a variable ass I think we should probably avoid swear words in case Microsoft runs their Policheck tool against this code.",
47+
RelativePath = "src/GitHub.App/ViewModels/PullRequestListViewModel.cs",
48+
},
49+
};
50+
51+
OutdatedFileComments = new[]
52+
{
53+
new PullRequestReviewFileCommentViewModelDesigner
54+
{
55+
Body = @"So this is just casting a mutable list to an IReadOnlyList which can be cast back to List. I know we probably won't do that, but I'm thinking of the next person to come along. The safe thing to do is to wrap List with a ReadOnlyList. We have an extension method ToReadOnlyList for observables. Wouldn't be hard to write one for IEnumerable.",
56+
RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs",
57+
},
58+
};
59+
}
60+
61+
public string Body { get; }
62+
public IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; set; }
63+
public bool IsExpanded { get; set; }
64+
public bool HasDetails { get; set; }
65+
public ILocalRepositoryModel LocalRepository { get; set; }
66+
public IPullRequestReviewModel Model { get; set; }
67+
public ReactiveCommand<object> NavigateToPullRequest { get; }
68+
public IReadOnlyList<IPullRequestReviewFileCommentViewModel> OutdatedFileComments { get; set; }
69+
public IPullRequestModel PullRequestModel { get; set; }
70+
public string RemoteRepositoryOwner { get; set; }
71+
public string StateDisplay { get; set; }
72+
}
73+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Threading.Tasks;
5+
using GitHub.Models;
6+
using GitHub.ViewModels.GitHubPane;
7+
using ReactiveUI;
8+
9+
namespace GitHub.SampleData
10+
{
11+
[ExcludeFromCodeCoverage]
12+
public class PullRequestUserReviewsViewModelDesigner : PanePageViewModelBase, IPullRequestUserReviewsViewModel
13+
{
14+
public PullRequestUserReviewsViewModelDesigner()
15+
{
16+
User = new AccountDesigner { Login = "Haacked", IsUser = true };
17+
PullRequestNumber = 123;
18+
PullRequestTitle = "Error handling/bubbling from viewmodels to views to viewhosts";
19+
Reviews = new[]
20+
{
21+
new PullRequestReviewViewModelDesigner()
22+
{
23+
IsExpanded = true,
24+
HasDetails = true,
25+
FileComments = new PullRequestReviewFileCommentViewModel[0],
26+
StateDisplay = "approved",
27+
Model = new PullRequestReviewModel
28+
{
29+
State = PullRequestReviewState.Approved,
30+
SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1),
31+
User = User,
32+
},
33+
},
34+
new PullRequestReviewViewModelDesigner()
35+
{
36+
IsExpanded = true,
37+
HasDetails = true,
38+
StateDisplay = "requested changes",
39+
Model = new PullRequestReviewModel
40+
{
41+
State = PullRequestReviewState.ChangesRequested,
42+
SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2),
43+
User = User,
44+
},
45+
},
46+
new PullRequestReviewViewModelDesigner()
47+
{
48+
IsExpanded = false,
49+
HasDetails = false,
50+
StateDisplay = "commented",
51+
Model = new PullRequestReviewModel
52+
{
53+
State = PullRequestReviewState.Commented,
54+
SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2),
55+
User = User,
56+
},
57+
}
58+
};
59+
}
60+
61+
public ILocalRepositoryModel LocalRepository { get; set; }
62+
public string RemoteRepositoryOwner { get; set; }
63+
public int PullRequestNumber { get; set; }
64+
public IAccount User { get; set; }
65+
public IReadOnlyList<IPullRequestReviewViewModel> Reviews { get; set; }
66+
public string PullRequestTitle { get; set; }
67+
public ReactiveCommand<object> NavigateToPullRequest { get; }
68+
69+
public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int pullRequestNumber, string login)
70+
{
71+
return Task.CompletedTask;
72+
}
73+
}
74+
}

src/GitHub.App/Services/ModelService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ public IObservable<IAccount> GetCurrentUser()
5757
return GetUserFromCache().Select(Create);
5858
}
5959

60+
public IObservable<IAccount> GetUser(string login)
61+
{
62+
return hostCache.GetAndRefreshObject("user|" + login,
63+
() => ApiClient.GetUser(login).Select(AccountCacheItem.Create), TimeSpan.FromMinutes(5), TimeSpan.FromDays(7))
64+
.Select(Create);
65+
}
66+
6067
public IObservable<GitIgnoreItem> GetGitIgnoreTemplates()
6168
{
6269
return Observable.Defer(() =>
@@ -398,6 +405,7 @@ async Task<IList<IPullRequestReviewModel>> GetPullRequestReviews(string owner, s
398405
Body = y.Body,
399406
CommitId = y.Commit.Oid,
400407
State = FromGraphQL(y.State),
408+
SubmittedAt = y.SubmittedAt,
401409
User = Create(y.Author.Login, y.Author.AvatarUrl(null))
402410
}).ToList()
403411
});
@@ -623,6 +631,7 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem)
623631
Body = x.Body,
624632
State = x.State,
625633
CommitId = x.CommitId,
634+
SubmittedAt = x.SubmittedAt,
626635
}).ToList(),
627636
ReviewComments = prCacheItem.ReviewComments.Select(x =>
628637
(IPullRequestReviewCommentModel)new PullRequestReviewCommentModel
@@ -892,6 +901,7 @@ public PullRequestReviewCacheItem(IPullRequestReviewModel review)
892901
};
893902
Body = review.Body;
894903
State = review.State;
904+
SubmittedAt = review.SubmittedAt;
895905
}
896906

897907
public long Id { get; set; }
@@ -900,6 +910,7 @@ public PullRequestReviewCacheItem(IPullRequestReviewModel review)
900910
public string Body { get; set; }
901911
public GitHub.Models.PullRequestReviewState State { get; set; }
902912
public string CommitId { get; set; }
913+
public DateTimeOffset? SubmittedAt { get; set; }
903914
}
904915

905916
public class PullRequestReviewCommentCacheItem

src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
3333
{
3434
static readonly ILogger log = LogManager.ForContext<GitHubPaneViewModel>();
3535
static readonly Regex pullUri = CreateRoute("/:owner/:repo/pull/:number");
36+
static readonly Regex pullUserReviewsUri = CreateRoute("/:owner/:repo/pull/:number/reviews/:login");
3637

3738
readonly IViewViewModelFactory viewModelFactory;
3839
readonly ISimpleApiClientFactory apiClientFactory;
@@ -243,6 +244,14 @@ public async Task NavigateTo(Uri uri)
243244
var number = int.Parse(match.Groups["number"].Value);
244245
await ShowPullRequest(owner, repo, number);
245246
}
247+
else if ((match = pullUserReviewsUri.Match(uri.AbsolutePath))?.Success == true)
248+
{
249+
var owner = match.Groups["owner"].Value;
250+
var repo = match.Groups["repo"].Value;
251+
var number = int.Parse(match.Groups["number"].Value);
252+
var login = match.Groups["login"].Value;
253+
await ShowPullRequestReviews(owner, repo, number, login);
254+
}
246255
else
247256
{
248257
throw new NotSupportedException("Unrecognised GitHub pane URL: " + uri.AbsolutePath);
@@ -282,6 +291,20 @@ public Task ShowPullRequest(string owner, string repo, int number)
282291
x => x.RemoteRepositoryOwner == owner && x.LocalRepository.Name == repo && x.Number == number);
283292
}
284293

294+
/// <inheritdoc/>
295+
public Task ShowPullRequestReviews(string owner, string repo, int number, string login)
296+
{
297+
Guard.ArgumentNotNull(owner, nameof(owner));
298+
Guard.ArgumentNotNull(repo, nameof(repo));
299+
300+
return NavigateTo<IPullRequestUserReviewsViewModel>(
301+
x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number, login),
302+
x => x.RemoteRepositoryOwner == owner &&
303+
x.LocalRepository.Name == repo &&
304+
x.PullRequestNumber == number &&
305+
x.User.Login == login);
306+
}
307+
285308
async Task CreateInitializeTask(IServiceProvider paneServiceProvider)
286309
{
287310
await UpdateContent(teamExplorerContext.ActiveRepository);
@@ -408,7 +431,7 @@ static async Task<bool> IsValidRepository(ISimpleApiClient client)
408431
static Regex CreateRoute(string route)
409432
{
410433
// Build RegEx from route (:foo to named group (?<foo>[\w_.-]+)).
411-
var routeFormat = new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)");
434+
var routeFormat = "^" + new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)") + "$";
412435
return new Regex(routeFormat, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
413436
}
414437
}

src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using LibGit2Sharp;
1919
using ReactiveUI;
2020
using Serilog;
21+
using static System.FormattableString;
2122

2223
namespace GitHub.ViewModels.GitHubPane
2324
{
@@ -636,7 +637,16 @@ async Task DoSyncSubmodules(object unused)
636637

637638
void DoShowReview(object item)
638639
{
639-
// TODO
640+
var review = (PullRequestReviewSummaryViewModel)item;
641+
642+
if (review.State == PullRequestReviewState.Pending)
643+
{
644+
throw new NotImplementedException();
645+
}
646+
else
647+
{
648+
NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/reviews/{review.User.Login}"));
649+
}
640650
}
641651

642652
class CheckoutCommandState : IPullRequestCheckoutState
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Reactive;
4+
using GitHub.Extensions;
5+
using GitHub.Models;
6+
using GitHub.Services;
7+
using ReactiveUI;
8+
9+
namespace GitHub.ViewModels.GitHubPane
10+
{
11+
/// <summary>
12+
/// A view model for a file comment in a <see cref="PullRequestReviewViewModel"/>.
13+
/// </summary>
14+
public class PullRequestReviewFileCommentViewModel : IPullRequestReviewFileCommentViewModel
15+
{
16+
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This will be used in a later PR")]
17+
readonly IPullRequestEditorService editorService;
18+
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This will be used in a later PR")]
19+
readonly IPullRequestSession session;
20+
readonly IPullRequestReviewCommentModel model;
21+
22+
public PullRequestReviewFileCommentViewModel(
23+
IPullRequestEditorService editorService,
24+
IPullRequestSession session,
25+
IPullRequestReviewCommentModel model)
26+
{
27+
Guard.ArgumentNotNull(editorService, nameof(editorService));
28+
Guard.ArgumentNotNull(session, nameof(session));
29+
Guard.ArgumentNotNull(model, nameof(model));
30+
31+
this.editorService = editorService;
32+
this.session = session;
33+
this.model = model;
34+
}
35+
36+
/// <inheritdoc/>
37+
public string Body => model.Body;
38+
39+
/// <inheritdoc/>
40+
public string RelativePath => model.Path;
41+
42+
/// <inheritdoc/>
43+
public ReactiveCommand<Unit> Open { get; }
44+
}
45+
}

0 commit comments

Comments
 (0)