Pull Requests w/ Statuses #1788
Changes from 42 commits
d1d60ff
bfd6bfb
8622f21
518c8c0
c3d3b1c
047ba1a
ae00f33
c6cd36f
eb575e6
756e733
389d49a
9c60db1
2405755
42bb263
533506f
42fbab6
b086078
bade798
6d4c4ca
e7e26d9
2f3e739
2236973
10b1fdd
7107bdc
4074fea
5a7775c
9c6c5da
276549b
5c2c0f7
5edce94
af979f2
9db38a9
d279d06
86b3942
b0cdcf0
7ced79b
967e91d
e890055
0974944
b45bcc8
5c2b93c
035915d
ee83908
2b8cee3
f964972
14d5eca
a026bab
6a6c565
cfa7c20
35ba4b2
e6bf1bf
d5d633e
f9f8d9a
3639807
48f180d
e55e47a
d6a95f7
1fe4822
86df2bc
01efca2
57b4f31
923ad51
f4d59bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ | |
using GitHub.Primitives; | ||
using GitHub.VisualStudio.Helpers; | ||
using System.Diagnostics; | ||
using System.Collections.Generic; | ||
using GitHub.Extensions; | ||
|
||
namespace GitHub.Models | ||
|
@@ -125,6 +124,17 @@ public PullRequestStateEnum State | |
} | ||
} | ||
|
||
PullRequestChecksEnum checks; | ||
public PullRequestChecksEnum Checks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this actually needs to be added to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see... |
||
{ | ||
get { return checks; } | ||
set | ||
{ | ||
checks = value; | ||
this.RaisePropertyChange(); | ||
} | ||
} | ||
|
||
// TODO: Remove these property once maintainer workflow has been merged to master. | ||
public bool IsOpen => State == PullRequestStateEnum.Open; | ||
public bool Merged => State == PullRequestStateEnum.Merged; | ||
|
@@ -183,4 +193,4 @@ internal string DebuggerDisplay | |
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,17 @@ public class PullRequestService : IPullRequestService | |
Items = page.Nodes.Select(pr => new ListItemAdapter | ||
{ | ||
Id = pr.Id.Value, | ||
LastCommit = pr.Commits(null, null, 1, null).Nodes.Select(commit => | ||
new LastCommitSummaryModel | ||
{ | ||
Statuses = commit.Commit.Status | ||
.Select(context => | ||
context.Contexts.Select(statusContext => new StatusSummaryModel | ||
{ | ||
State = (StatusStateEnum)statusContext.State, | ||
}).ToList() | ||
).SingleOrDefault() | ||
}).ToList().FirstOrDefault(), | ||
Author = new ActorModel | ||
{ | ||
Login = pr.Author.Login, | ||
|
@@ -123,10 +134,46 @@ public class PullRequestService : IPullRequestService | |
|
||
var result = await graphql.Run(readPullRequests, vars); | ||
|
||
foreach (ListItemAdapter item in result.Items) | ||
foreach (var item in result.Items.Cast<ListItemAdapter>()) | ||
{ | ||
item.CommentCount += item.Reviews.Sum(x => x.Count); | ||
item.Reviews = null; | ||
|
||
var hasStatuses = item.LastCommit.Statuses != null | ||
&& item.LastCommit.Statuses.Any(); | ||
|
||
if (!hasStatuses) | ||
{ | ||
item.Checks = PullRequestChecksEnum.None; | ||
} | ||
else | ||
{ | ||
var statusHasFailure = item.LastCommit | ||
.Statuses | ||
.Any(status => status.State == StatusStateEnum.Failure); | ||
|
||
var statusHasCompleteSuccess = true; | ||
if (!statusHasFailure) | ||
{ | ||
statusHasCompleteSuccess = | ||
item.LastCommit.Statuses.All(status => status.State == StatusStateEnum.Success); | ||
} | ||
|
||
if (statusHasFailure) | ||
{ | ||
item.Checks = PullRequestChecksEnum.Failure; | ||
} | ||
else if (statusHasCompleteSuccess) | ||
{ | ||
item.Checks = PullRequestChecksEnum.Success; | ||
} | ||
else | ||
{ | ||
item.Checks = PullRequestChecksEnum.Pending; | ||
} | ||
} | ||
|
||
item.LastCommit = null; | ||
} | ||
|
||
return result; | ||
|
@@ -840,6 +887,8 @@ static string BuildGHfVSConfigKeyValue(string owner, int number) | |
class ListItemAdapter : PullRequestListItemModel | ||
{ | ||
public IList<ReviewAdapter> Reviews { get; set; } | ||
|
||
public LastCommitSummaryModel LastCommit { get; set; } | ||
} | ||
|
||
class ReviewAdapter | ||
|
@@ -848,5 +897,15 @@ class ReviewAdapter | |
public int CommentCount { get; set; } | ||
public int Count => CommentCount + (!string.IsNullOrWhiteSpace(Body) ? 1 : 0); | ||
} | ||
|
||
class LastCommitSummaryModel | ||
{ | ||
public List<StatusSummaryModel> Statuses { get; set; } | ||
} | ||
} | ||
|
||
public class StatusSummaryModel | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this model class is only used within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
{ | ||
public StatusStateEnum State { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,119 @@ | ||||||||||||||||||||
using System; | ||||||||||||||||||||
using System.Collections.Generic; | ||||||||||||||||||||
using System.ComponentModel.Composition; | ||||||||||||||||||||
using System.Linq; | ||||||||||||||||||||
using System.Reactive; | ||||||||||||||||||||
using System.Reactive.Linq; | ||||||||||||||||||||
using System.Windows.Media.Imaging; | ||||||||||||||||||||
using GitHub.Extensions; | ||||||||||||||||||||
using GitHub.Factories; | ||||||||||||||||||||
using GitHub.Models; | ||||||||||||||||||||
using GitHub.Services; | ||||||||||||||||||||
using ReactiveUI; | ||||||||||||||||||||
|
||||||||||||||||||||
namespace GitHub.ViewModels.GitHubPane | ||||||||||||||||||||
{ | ||||||||||||||||||||
[Export(typeof(IPullRequestCheckViewModel))] | ||||||||||||||||||||
[PartCreationPolicy(CreationPolicy.NonShared)] | ||||||||||||||||||||
public class PullRequestCheckViewModel: ViewModelBase, IPullRequestCheckViewModel | ||||||||||||||||||||
{ | ||||||||||||||||||||
private readonly IUsageTracker usageTracker; | ||||||||||||||||||||
const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"; | ||||||||||||||||||||
|
||||||||||||||||||||
private string title; | ||||||||||||||||||||
private string description; | ||||||||||||||||||||
private PullRequestCheckStatusEnum status; | ||||||||||||||||||||
private Uri detailsUrl; | ||||||||||||||||||||
private string avatarUrl; | ||||||||||||||||||||
private BitmapImage avatar; | ||||||||||||||||||||
|
||||||||||||||||||||
public static IEnumerable<IPullRequestCheckViewModel> Build(IViewViewModelFactory viewViewModelFactory, PullRequestDetailModel pullRequest) | ||||||||||||||||||||
{ | ||||||||||||||||||||
return pullRequest.Statuses?.Select(model => | ||||||||||||||||||||
{ | ||||||||||||||||||||
var statusStateEnum = model.State; | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the additional variable here? It's only used once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool |
||||||||||||||||||||
|
||||||||||||||||||||
PullRequestCheckStatusEnum checkStatus; | ||||||||||||||||||||
switch (statusStateEnum) | ||||||||||||||||||||
{ | ||||||||||||||||||||
case StatusStateEnum.Expected: | ||||||||||||||||||||
case StatusStateEnum.Error: | ||||||||||||||||||||
case StatusStateEnum.Failure: | ||||||||||||||||||||
checkStatus = PullRequestCheckStatusEnum.Failure; | ||||||||||||||||||||
break; | ||||||||||||||||||||
case StatusStateEnum.Pending: | ||||||||||||||||||||
checkStatus = PullRequestCheckStatusEnum.Pending; | ||||||||||||||||||||
break; | ||||||||||||||||||||
case StatusStateEnum.Success: | ||||||||||||||||||||
checkStatus = PullRequestCheckStatusEnum.Success; | ||||||||||||||||||||
break; | ||||||||||||||||||||
default: | ||||||||||||||||||||
throw new InvalidOperationException("Unkown PullRequestCheckStatusEnum"); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
var pullRequestCheckViewModel = viewViewModelFactory.CreateViewModel<IPullRequestCheckViewModel>(); | ||||||||||||||||||||
pullRequestCheckViewModel.Title = model.Context; | ||||||||||||||||||||
pullRequestCheckViewModel.Description = model.Description; | ||||||||||||||||||||
pullRequestCheckViewModel.Status = checkStatus; | ||||||||||||||||||||
pullRequestCheckViewModel.DetailsUrl = new Uri(model.TargetUrl); | ||||||||||||||||||||
pullRequestCheckViewModel.AvatarUrl = model.AvatarUrl ?? DefaultAvatar; | ||||||||||||||||||||
pullRequestCheckViewModel.Avatar = model.AvatarUrl != null | ||||||||||||||||||||
? new BitmapImage(new Uri(model.AvatarUrl)) | ||||||||||||||||||||
: AvatarProvider.CreateBitmapImage(DefaultAvatar); | ||||||||||||||||||||
|
||||||||||||||||||||
return pullRequestCheckViewModel; | ||||||||||||||||||||
|
||||||||||||||||||||
}) ?? new PullRequestCheckViewModel[0]; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
[ImportingConstructor] | ||||||||||||||||||||
public PullRequestCheckViewModel(IUsageTracker usageTracker) | ||||||||||||||||||||
{ | ||||||||||||||||||||
this.usageTracker = usageTracker; | ||||||||||||||||||||
OpenDetailsUrl = ReactiveCommand.Create().OnExecuteCompleted(DoOpenDetailsUrl); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
private void DoOpenDetailsUrl(object obj) | ||||||||||||||||||||
{ | ||||||||||||||||||||
usageTracker.IncrementCounter(x => x.NumberOfPRCheckStatusesOpenInGitHub).Forget(); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public string Title | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return title; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref title, value); } | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can these properties change? If not then they shouldn't be settable and don't need to raise property changed events. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They don't change, but they are being set here... VisualStudio/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs Lines 54 to 62 in 14d5eca
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, but I see the point.. |
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public string Description | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return description; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref description, value); } | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public PullRequestCheckStatusEnum Status | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return status; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref status, value); } | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public Uri DetailsUrl | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return detailsUrl; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref detailsUrl, value); } | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public string AvatarUrl | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return avatarUrl; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref avatarUrl, value); } | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public BitmapImage Avatar | ||||||||||||||||||||
{ | ||||||||||||||||||||
get { return avatar; } | ||||||||||||||||||||
set { this.RaiseAndSetIfChanged(ref avatar, value); } | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public ReactiveCommand<object> OpenDetailsUrl { get; } | ||||||||||||||||||||
} | ||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,8 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq | |
bool refreshOnActivate; | ||
Uri webUrl; | ||
IDisposable sessionSubscription; | ||
IViewViewModelFactory viewViewModelFactory; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
IReadOnlyList<IPullRequestCheckViewModel> checks; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="PullRequestDetailViewModel"/> class. | ||
|
@@ -73,21 +75,24 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq | |
IUsageTracker usageTracker, | ||
ITeamExplorerContext teamExplorerContext, | ||
IPullRequestFilesViewModel files, | ||
ISyncSubmodulesCommand syncSubmodulesCommand) | ||
ISyncSubmodulesCommand syncSubmodulesCommand, | ||
IViewViewModelFactory viewViewModelFactory) | ||
{ | ||
Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); | ||
Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); | ||
Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); | ||
Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); | ||
Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); | ||
Guard.ArgumentNotNull(syncSubmodulesCommand, nameof(syncSubmodulesCommand)); | ||
Guard.ArgumentNotNull(viewViewModelFactory, nameof(viewViewModelFactory)); | ||
|
||
this.pullRequestsService = pullRequestsService; | ||
this.sessionManager = sessionManager; | ||
this.modelServiceFactory = modelServiceFactory; | ||
this.usageTracker = usageTracker; | ||
this.teamExplorerContext = teamExplorerContext; | ||
this.syncSubmodulesCommand = syncSubmodulesCommand; | ||
this.viewViewModelFactory = viewViewModelFactory; | ||
Files = files; | ||
|
||
Checkout = ReactiveCommand.CreateAsyncObservable( | ||
|
@@ -302,6 +307,12 @@ public Uri WebUrl | |
/// </summary> | ||
public ReactiveCommand<object> ShowReview { get; } | ||
|
||
public IReadOnlyList<IPullRequestCheckViewModel> Checks | ||
{ | ||
get { return checks; } | ||
private set { this.RaiseAndSetIfChanged(ref checks, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the view model. | ||
/// </summary> | ||
|
@@ -377,6 +388,8 @@ public async Task Load(PullRequestDetailModel pullRequest) | |
Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; | ||
Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, pullRequest).ToList(); | ||
|
||
Checks = PullRequestCheckViewModel.Build(viewViewModelFactory, pullRequest)?.ToList(); | ||
|
||
await Files.InitializeAsync(Session); | ||
|
||
var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Would be nice to completely remove the changes to these unchanged files to give a cleaner diff ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😸