diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 30530e0ee8..1750094b76 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -257,6 +257,7 @@ + @@ -267,6 +268,7 @@ + diff --git a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs new file mode 100644 index 0000000000..7b7cc105f6 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public sealed class PullRequestCheckViewModelDesigner : ViewModelBase, IPullRequestCheckViewModel + { + public string Title { get; set; } = "continuous-integration/appveyor/pr"; + + public string Description { get; set; } = "AppVeyor build failed"; + + public PullRequestCheckStatus Status { get; set; } = PullRequestCheckStatus.Failure; + + public Uri DetailsUrl { get; set; } = new Uri("http://github.com"); + + public string AvatarUrl { get; set; } = "https://avatars1.githubusercontent.com/u/417571?s=88&v=4"; + + public BitmapImage Avatar { get; set; } = null; + + public ReactiveCommand OpenDetailsUrl { get; set; } = null; + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index a174c3e299..bb1ac8a3c5 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Reactive; -using System.Text; -using System.Threading.Tasks; using GitHub.Models; using GitHub.Services; using GitHub.ViewModels; using GitHub.ViewModels.GitHubPane; using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.SampleData; namespace GitHub.SampleData { @@ -95,6 +95,8 @@ public PullRequestDetailViewModelDesigner() }; Files = new PullRequestFilesViewModelDesigner(); + + Checks = new PullRequestCheckViewModelDesigner[0]; } public PullRequestDetailModel Model { get; } @@ -123,6 +125,8 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand OpenOnGitHub { get; } public ReactiveCommand ShowReview { get; } + public IReadOnlyList Checks { get; } + public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask; public string GetLocalFilePath(IPullRequestFileNode file) diff --git a/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs index 4c00b0ba98..500089fb84 100644 --- a/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using GitHub.Models; using GitHub.ViewModels; using GitHub.ViewModels.GitHubPane; @@ -16,5 +17,6 @@ public class PullRequestListItemViewModelDesigner : ViewModelBase, IPullRequestL public int Number { get; set; } public string Title { get; set; } public DateTimeOffset UpdatedAt { get; set; } + public PullRequestChecksState Checks { get; set; } } } diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs index 5489cbbed9..f8b26e918a 100644 --- a/src/GitHub.App/Services/PullRequestService.cs +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -22,6 +22,7 @@ using Rothko; using static System.FormattableString; using static Octokit.GraphQL.Variable; +using StatusState = GitHub.Models.StatusState; namespace GitHub.Services { @@ -93,6 +94,17 @@ public async Task> ReadPullRequests( 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 = (StatusState)statusContext.State, + }).ToList() + ).SingleOrDefault() + }).ToList().FirstOrDefault(), Author = new ActorModel { Login = pr.Author.Login, @@ -123,10 +135,46 @@ public async Task> ReadPullRequests( var result = await graphql.Run(readPullRequests, vars); - foreach (ListItemAdapter item in result.Items) + foreach (var item in result.Items.Cast()) { 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 = PullRequestChecksState.None; + } + else + { + var statusHasFailure = item.LastCommit + .Statuses + .Any(status => status.State == StatusState.Failure); + + var statusHasCompleteSuccess = true; + if (!statusHasFailure) + { + statusHasCompleteSuccess = + item.LastCommit.Statuses.All(status => status.State == StatusState.Success); + } + + if (statusHasFailure) + { + item.Checks = PullRequestChecksState.Failure; + } + else if (statusHasCompleteSuccess) + { + item.Checks = PullRequestChecksState.Success; + } + else + { + item.Checks = PullRequestChecksState.Pending; + } + } + + item.LastCommit = null; } return result; @@ -840,6 +888,8 @@ static Tuple ParseGHfVSConfigKeyValue(string value) class ListItemAdapter : PullRequestListItemModel { public IList Reviews { get; set; } + + public LastCommitSummaryModel LastCommit { get; set; } } class ReviewAdapter @@ -848,5 +898,15 @@ class ReviewAdapter public int CommentCount { get; set; } public int Count => CommentCount + (!string.IsNullOrWhiteSpace(Body) ? 1 : 0); } + + class StatusSummaryModel + { + public StatusState State { get; set; } + } + + class LastCommitSummaryModel + { + public List Statuses { get; set; } + } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs new file mode 100644 index 0000000000..e705a58608 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs @@ -0,0 +1,86 @@ +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"; + + public static IEnumerable Build(IViewViewModelFactory viewViewModelFactory, PullRequestDetailModel pullRequest) + { + return pullRequest.Statuses?.Select(model => + { + PullRequestCheckStatus checkStatus; + switch (model.State) + { + case StatusState.Expected: + case StatusState.Error: + case StatusState.Failure: + checkStatus = PullRequestCheckStatus.Failure; + break; + case StatusState.Pending: + checkStatus = PullRequestCheckStatus.Pending; + break; + case StatusState.Success: + checkStatus = PullRequestCheckStatus.Success; + break; + default: + throw new InvalidOperationException("Unkown PullRequestCheckStatusEnum"); + } + + var pullRequestCheckViewModel = (PullRequestCheckViewModel) viewViewModelFactory.CreateViewModel(); + 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; private set; } + + public string Description { get; private set; } + + public PullRequestCheckStatus Status{ get; private set; } + + public Uri DetailsUrl { get; private set; } + + public string AvatarUrl { get; private set; } + + public BitmapImage Avatar { get; private set; } + + public ReactiveCommand OpenDetailsUrl { get; } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index 38a1d4b478..643d38ab6f 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -38,6 +38,7 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq readonly IUsageTracker usageTracker; readonly ITeamExplorerContext teamExplorerContext; readonly ISyncSubmodulesCommand syncSubmodulesCommand; + readonly IViewViewModelFactory viewViewModelFactory; IModelService modelService; PullRequestDetailModel model; IActorViewModel author; @@ -55,6 +56,7 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq bool refreshOnActivate; Uri webUrl; IDisposable sessionSubscription; + IReadOnlyList checks; /// /// Initializes a new instance of the class. @@ -73,7 +75,8 @@ public PullRequestDetailViewModel( IUsageTracker usageTracker, ITeamExplorerContext teamExplorerContext, IPullRequestFilesViewModel files, - ISyncSubmodulesCommand syncSubmodulesCommand) + ISyncSubmodulesCommand syncSubmodulesCommand, + IViewViewModelFactory viewViewModelFactory) { Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); @@ -81,6 +84,7 @@ public PullRequestDetailViewModel( 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; @@ -88,6 +92,7 @@ public PullRequestDetailViewModel( this.usageTracker = usageTracker; this.teamExplorerContext = teamExplorerContext; this.syncSubmodulesCommand = syncSubmodulesCommand; + this.viewViewModelFactory = viewViewModelFactory; Files = files; Checkout = ReactiveCommand.CreateAsyncObservable( @@ -302,6 +307,12 @@ public Uri WebUrl /// public ReactiveCommand ShowReview { get; } + public IReadOnlyList Checks + { + get { return checks; } + private set { this.RaiseAndSetIfChanged(ref checks, value); } + } + /// /// Initializes the view model. /// @@ -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(); diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs index b9355c0d8f..b60cf0e70b 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs @@ -19,6 +19,7 @@ public PullRequestListItemViewModel(PullRequestListItemModel model) { Id = model.Id; Author = new ActorViewModel(model.Author); + Checks = model.Checks; CommentCount = model.CommentCount; Number = model.Number; Title = model.Title; @@ -26,13 +27,16 @@ public PullRequestListItemViewModel(PullRequestListItemModel model) } /// - public string Id { get; protected set; } + public string Id { get; } /// - public IActorViewModel Author { get; protected set; } + public IActorViewModel Author { get; } /// - public int CommentCount { get; protected set; } + public PullRequestChecksState Checks { get; } + + /// + public int CommentCount { get; } /// public bool IsCurrent @@ -42,12 +46,12 @@ public bool IsCurrent } /// - public int Number { get; protected set; } + public int Number { get; } /// - public string Title { get; protected set; } + public string Title { get; } /// - public DateTimeOffset UpdatedAt { get; protected set; } + public DateTimeOffset UpdatedAt { get; } } } diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index b634ab7a9b..1cc6c73eec 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -204,6 +204,7 @@ + diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs new file mode 100644 index 0000000000..3793468823 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Represents a view model for displaying details of a pull request Status or Check. + /// + public interface IPullRequestCheckViewModel: IViewModel + { + /// + /// The title of the Status/Check + /// + string Title { get; } + + /// + /// The description of the Status/Check + /// + string Description { get; } + + /// + /// The status of the Status/Check + /// + PullRequestCheckStatus Status { get; } + + /// + /// The url where more information about the Status/Check can be found + /// + Uri DetailsUrl { get; } + + /// + /// The AvatarUrl of the Status/Check application + /// + string AvatarUrl { get; } + + /// + /// The BitmapImage of the AvatarUrl + /// + BitmapImage Avatar { get; } + + /// + /// A command that opens the DetailsUrl in a browser + /// + + ReactiveCommand OpenDetailsUrl { get; } + } + + public enum PullRequestCheckStatus + { + Pending, + Success, + Failure + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index 9a11d0dd40..73ba93d390 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -175,6 +175,11 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// ReactiveCommand ShowReview { get; } + /// + /// Gets the latest pull request Checks & Statuses + /// + IReadOnlyList Checks { get; } + /// /// Initializes the view model. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs index b252e2a60c..f8fa89c351 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs @@ -1,4 +1,5 @@ using System; +using GitHub.Models; namespace GitHub.ViewModels.GitHubPane { @@ -27,5 +28,10 @@ public interface IPullRequestListItemViewModel : IIssueListItemViewModelBase /// Gets the last updated time of the pull request. /// DateTimeOffset UpdatedAt { get; } + + /// + /// Gets the pull request checks and statuses summary + /// + PullRequestChecksState Checks { get; } } } diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 36ceeaca74..ad7f0e87cf 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -185,6 +185,8 @@ + + diff --git a/src/GitHub.Exports/Models/IPullRequestModel.cs b/src/GitHub.Exports/Models/IPullRequestModel.cs index 8ff71e861c..4d78de08ab 100644 --- a/src/GitHub.Exports/Models/IPullRequestModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestModel.cs @@ -13,6 +13,14 @@ public enum PullRequestStateEnum Merged, } + public enum PullRequestChecksState + { + None, + Pending, + Success, + Failure + } + public interface IPullRequestModel : ICopyable, IEquatable, IComparable { diff --git a/src/GitHub.Exports/Models/PullRequestDetailModel.cs b/src/GitHub.Exports/Models/PullRequestDetailModel.cs index ee6ce03a67..3cd2de8a58 100644 --- a/src/GitHub.Exports/Models/PullRequestDetailModel.cs +++ b/src/GitHub.Exports/Models/PullRequestDetailModel.cs @@ -91,5 +91,10 @@ public class PullRequestDetailModel /// into threads, as such each pull request review comment will appear in both collections. /// public IReadOnlyList Threads { get; set; } + + /// + /// Gets or sets a collection of pull request Checks & Statuses + /// + public List Statuses { get; set; } } } diff --git a/src/GitHub.Exports/Models/PullRequestListItemModel.cs b/src/GitHub.Exports/Models/PullRequestListItemModel.cs index 21f6030429..f6a9a7fe55 100644 --- a/src/GitHub.Exports/Models/PullRequestListItemModel.cs +++ b/src/GitHub.Exports/Models/PullRequestListItemModel.cs @@ -37,6 +37,11 @@ public class PullRequestListItemModel /// public PullRequestStateEnum State { get; set; } + /// + /// Gets the pull request checks and statuses summary + /// + public PullRequestChecksState Checks { get; set; } + /// /// Gets or sets the date/time at which the pull request was last updated. /// diff --git a/src/GitHub.Exports/Models/StatusModel.cs b/src/GitHub.Exports/Models/StatusModel.cs new file mode 100644 index 0000000000..9d4998a494 --- /dev/null +++ b/src/GitHub.Exports/Models/StatusModel.cs @@ -0,0 +1,33 @@ +namespace GitHub.Models +{ + /// + /// Holds details about a pull request Status + /// + public class StatusModel + { + /// + /// The state of the Status + /// + public StatusState State { get; set; } + + /// + /// The Status context or title + /// + public string Context { get; set; } + + /// + /// The url where more information about the Status can be found + /// + public string TargetUrl { get; set; } + + /// + /// The descritption for the Status + /// + public string Description { get; set; } + + /// + /// The Url for the avatar for the Status + /// + public string AvatarUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/StatusState.cs b/src/GitHub.Exports/Models/StatusState.cs new file mode 100644 index 0000000000..57830b3dd0 --- /dev/null +++ b/src/GitHub.Exports/Models/StatusState.cs @@ -0,0 +1,11 @@ +namespace GitHub.Models +{ + public enum StatusState + { + Expected, + Error, + Failure, + Pending, + Success, + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index 17dd22b1bb..4a668c13ec 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -58,6 +58,8 @@ public class MeasuresModel public int NumberOfWelcomeDocsClicks { get; set; } public int NumberOfWelcomeTrainingClicks { get; set; } public int NumberOfGitHubPaneHelpClicks { get; set; } + public int NumberOfPRDetailsOpenInGitHub { get; set; } + public int NumberOfPRCheckStatusesOpenInGitHub { get; set; } public int NumberOfPRDetailsViewChanges { get; set; } public int NumberOfPRDetailsViewFile { get; set; } public int NumberOfPRDetailsCompareWithSolution { get; set; } diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs index a23ae8b38f..ccc89a6dfd 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -24,6 +24,7 @@ using Serilog; using PullRequestReviewEvent = Octokit.PullRequestReviewEvent; using static Octokit.GraphQL.Variable; +using StatusState = GitHub.Models.StatusState; // GraphQL DatabaseId field are marked as deprecated, but we need them for interop with REST. #pragma warning disable CS0618 @@ -38,6 +39,7 @@ public class PullRequestSessionService : IPullRequestSessionService { static readonly ILogger log = LogManager.ForContext(); static ICompiledQuery readPullRequest; + static ICompiledQuery> readCommitStatuses; static ICompiledQuery readViewer; readonly IGitService gitService; @@ -341,6 +343,9 @@ public virtual async Task ReadPullRequestDetail(HostAddr var apiClient = await apiClientFactory.Create(address); var files = await apiClient.GetPullRequestFiles(owner, name, number).ToList(); + var lastCommitModel = await GetPullRequestLastCommitAdapter(address, owner, name, number); + + result.Statuses = lastCommitModel.Statuses; result.ChangedFiles = files.Select(file => new PullRequestFileModel { @@ -736,6 +741,42 @@ Task GetRepository(ILocalRepositoryModel repository) return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath)); } + async Task GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number) + { + if (readCommitStatuses == null) + { + readCommitStatuses = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select( + commit => new LastCommitAdapter + { + Statuses = commit.Commit.Status + .Select(context => + context.Contexts.Select(statusContext => new StatusModel + { + State = (StatusState)statusContext.State, + Context = statusContext.Context, + TargetUrl = statusContext.TargetUrl, + Description = statusContext.Description, + AvatarUrl = statusContext.Creator.AvatarUrl(null) + }).ToList() + ).SingleOrDefault() + } + ).Compile(); + } + + var vars = new Dictionary + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(number), number }, + }; + + var connection = await graphqlFactory.CreateConnection(address); + var result = await connection.Run(readCommitStatuses, vars); + return result.First(); + } + static void BuildPullRequestThreads(PullRequestDetailModel model) { var commentsByReplyId = new Dictionary>(); @@ -825,5 +866,10 @@ class CommentAdapter : PullRequestReviewCommentModel public string OriginalCommitId { get; set; } public string ReplyTo { get; set; } } - } + + class LastCommitAdapter + { + public List Statuses { get; set; } + } + } } diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index b554a80125..a7d3765a28 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -418,6 +418,9 @@ PullRequestReviewAuthoringView.xaml + + PullRequestCheckView.xaml + PullRequestReviewSummaryView.xaml @@ -602,6 +605,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml new file mode 100644 index 0000000000..c4dfc84771 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs new file mode 100644 index 0000000000..0bb8fe8d92 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericPullRequestCheckView : ViewBase { } + + [ExportViewFor(typeof(IPullRequestCheckViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestCheckView : GenericPullRequestCheckView + { + public PullRequestCheckView() + { + InitializeComponent(); + + this.WhenActivated(d => + { + d(ViewModel.OpenDetailsUrl.Subscribe(_ => DoOpenDetailsUrl())); + }); + } + + [Import] + IVisualStudioBrowser VisualStudioBrowser { get; set; } + + void DoOpenDetailsUrl() + { + var browser = VisualStudioBrowser; + browser.OpenUrl(ViewModel.DetailsUrl); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml index 8813d36270..5048b03ef9 100644 --- a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -245,6 +245,20 @@ + + + + + + + + + + + UpdatedAt="2018-01-29" + Checks="Success"> @@ -54,6 +55,7 @@ + - + @@ -78,28 +80,27 @@ - - - - - - - - - - - - - - by - - - + + + + - - + + + + by + - + + + + + + + + + + diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs index 758b6f220f..50df1cba03 100644 --- a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -604,7 +604,8 @@ static Tuple CreateTargetAndSer Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For()); vm.InitializeAsync(repository, Substitute.For(), "owner", "repo", 1).Wait(); return Tuple.Create(vm, pullRequestService);