diff --git a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs index 18a6b30a8b..d28f198b7a 100644 --- a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs @@ -9,6 +9,8 @@ namespace GitHub.SampleData { public sealed class PullRequestCheckViewModelDesigner : ViewModelBase, IPullRequestCheckViewModel { + public bool IsRequired { get; } = true; + public string Title { get; set; } = "continuous-integration/appveyor/pr"; public string Description { get; set; } = "AppVeyor build failed"; diff --git a/src/GitHub.App/Services/RepositoryService.cs b/src/GitHub.App/Services/RepositoryService.cs index f713cabbb7..fb372e6b94 100644 --- a/src/GitHub.App/Services/RepositoryService.cs +++ b/src/GitHub.App/Services/RepositoryService.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Linq; using System.Threading.Tasks; using GitHub.Api; using GitHub.Extensions; +using GitHub.Models; using GitHub.Primitives; using Octokit.GraphQL; using static Octokit.GraphQL.Variable; @@ -12,9 +14,10 @@ namespace GitHub.Services { [Export(typeof(IRepositoryService))] [PartCreationPolicy(CreationPolicy.Shared)] - public class RepositoryService : IRepositoryService + public class RepositoryService: IRepositoryService { static ICompiledQuery> readParentOwnerLogin; + static ICompiledQuery> queryProtectedBranches; readonly IGraphQLClientFactory graphqlFactory; [ImportingConstructor] @@ -45,9 +48,47 @@ public RepositoryService(IGraphQLClientFactory graphqlFactory) { nameof(name), name }, }; - var graphql = await graphqlFactory.CreateConnection(address); - var result = await graphql.Run(readParentOwnerLogin, vars); + var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false); + var result = await graphql.Run(readParentOwnerLogin, vars).ConfigureAwait(false); return result != null ? (result.Item1, result.Item2) : ((string, string)?)null; } + + public async Task> GetProtectedBranches(HostAddress address, string owner, string name) + { + Guard.ArgumentNotNull(address, nameof(address)); + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + if (queryProtectedBranches == null) + { + queryProtectedBranches = new Query() + .Repository(Var(nameof(name)), Var(nameof(owner))) + .Select(r => + r.ProtectedBranches(null, null, null, null) + .AllPages() + .Select(branch => new ProtectedBranch + { + Name = branch.Name, + RequiredStatusCheckContexts = branch.RequiredStatusCheckContexts.ToArray() + }).ToList() + ).Compile(); + } + + var vars = new Dictionary + { + { nameof(owner), owner }, + { nameof(name), name }, + }; + + var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false); + return await graphql.Run(queryProtectedBranches, vars).ConfigureAwait(false); + } + + public async Task GetProtectedBranch(HostAddress address, string owner, string name, string branchName) + { + Guard.ArgumentNotNull(branchName, nameof(branchName)); + var protectedBranches = await GetProtectedBranches(address, owner, name).ConfigureAwait(false); + return protectedBranches.FirstOrDefault(branch => branch.Name.Equals(branchName, StringComparison.InvariantCultureIgnoreCase)); + } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs index d3ceed939b..f1800bea54 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs @@ -53,6 +53,7 @@ public static IEnumerable Build(IViewViewModelFactor var pullRequestCheckViewModel = (PullRequestCheckViewModel) viewViewModelFactory.CreateViewModel(); pullRequestCheckViewModel.CheckType = PullRequestCheckType.StatusApi; + pullRequestCheckViewModel.IsRequired = statusModel.IsRequired; pullRequestCheckViewModel.Title = statusModel.Context; pullRequestCheckViewModel.Description = statusModel.Description; pullRequestCheckViewModel.Status = checkStatus; @@ -102,6 +103,7 @@ public static IEnumerable Build(IViewViewModelFactor var pullRequestCheckViewModel = (PullRequestCheckViewModel)viewViewModelFactory.CreateViewModel(); pullRequestCheckViewModel.CheckType = PullRequestCheckType.ChecksApi; + pullRequestCheckViewModel.IsRequired = arg.checkRun.IsRequired; pullRequestCheckViewModel.CheckRunId = arg.checkRun.Id; pullRequestCheckViewModel.HasAnnotations = arg.checkRun.Annotations?.Any() ?? false; pullRequestCheckViewModel.Title = arg.checkRun.Name; @@ -147,6 +149,9 @@ private void DoOpenDetailsUrl() usageTracker.IncrementCounter(expression).Forget(); } + /// + public bool IsRequired { get; private set; } + /// public string Title { get; private set; } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs index 05d48ad613..51b379f379 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs @@ -10,6 +10,8 @@ namespace GitHub.ViewModels.GitHubPane /// public interface IPullRequestCheckViewModel: IViewModel { + bool IsRequired { get; } + /// /// The title of the Status/Check. /// diff --git a/src/GitHub.Exports/Models/CheckRunModel.cs b/src/GitHub.Exports/Models/CheckRunModel.cs index 2587ab2a03..ba98d86638 100644 --- a/src/GitHub.Exports/Models/CheckRunModel.cs +++ b/src/GitHub.Exports/Models/CheckRunModel.cs @@ -58,6 +58,11 @@ public class CheckRunModel /// public string Summary { get; set; } + /// + /// The flag that denotes if this check is required for the pull request. + /// + public bool IsRequired { get; set; } + /// /// The detail of a Check Run. /// diff --git a/src/GitHub.Exports/Models/IPullRequestModel.cs b/src/GitHub.Exports/Models/IPullRequestModel.cs index 4d78de08ab..ae8b8f148d 100644 --- a/src/GitHub.Exports/Models/IPullRequestModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestModel.cs @@ -21,6 +21,16 @@ public enum PullRequestChecksState Failure } + public enum PullRequestChecksSummaryState + { + None, + Pending, + Success, + SuccessWithFailure, + SuccessWithPending, + Failure + } + public interface IPullRequestModel : ICopyable, IEquatable, IComparable { diff --git a/src/GitHub.Exports/Models/ProtectedBranch.cs b/src/GitHub.Exports/Models/ProtectedBranch.cs new file mode 100644 index 0000000000..019bc9ff2b --- /dev/null +++ b/src/GitHub.Exports/Models/ProtectedBranch.cs @@ -0,0 +1,8 @@ +namespace GitHub.Models +{ + public class ProtectedBranch + { + public string Name { get; set; } + public string[] RequiredStatusCheckContexts { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/StatusModel.cs b/src/GitHub.Exports/Models/StatusModel.cs index 3b0832caa0..a911bfa7e0 100644 --- a/src/GitHub.Exports/Models/StatusModel.cs +++ b/src/GitHub.Exports/Models/StatusModel.cs @@ -24,5 +24,7 @@ public class StatusModel /// The descritption for the Status /// public string Description { get; set; } + + public bool IsRequired { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IRepositoryService.cs b/src/GitHub.Exports/Services/IRepositoryService.cs index c71f492e29..c0e8ea4fd7 100644 --- a/src/GitHub.Exports/Services/IRepositoryService.cs +++ b/src/GitHub.Exports/Services/IRepositoryService.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; +using GitHub.Models; using GitHub.Primitives; namespace GitHub.Services @@ -17,5 +19,9 @@ public interface IRepositoryService /// otherwise null. /// Task<(string owner, string name)?> FindParent(HostAddress address, string owner, string name); + + Task> GetProtectedBranches(HostAddress address, string owner, string name); + + Task GetProtectedBranch(HostAddress address, string owner, string name, string branchName); } } diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs index 129b0a59db..6dd40a56a3 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -49,6 +49,7 @@ public class PullRequestSessionService : IPullRequestSessionService readonly IDiffService diffService; readonly IApiClientFactory apiClientFactory; readonly IGraphQLClientFactory graphqlFactory; + readonly IRepositoryService repositoryService; readonly IUsageTracker usageTracker; readonly IDictionary, string> mergeBaseCache; @@ -59,6 +60,7 @@ public PullRequestSessionService( IDiffService diffService, IApiClientFactory apiClientFactory, IGraphQLClientFactory graphqlFactory, + IRepositoryService repositoryService, IUsageTracker usageTracker) { this.gitService = gitService; @@ -66,6 +68,7 @@ public PullRequestSessionService( this.diffService = diffService; this.apiClientFactory = apiClientFactory; this.graphqlFactory = graphqlFactory; + this.repositoryService = repositoryService; this.usageTracker = usageTracker; mergeBaseCache = new Dictionary, string>(); @@ -360,9 +363,12 @@ public virtual async Task ReadPullRequestDetail(HostAddr var connection = await graphqlFactory.CreateConnection(address); var result = await connection.Run(readPullRequest, vars); + var protectedBranches = await repositoryService.GetProtectedBranch(address, owner, name, result.BaseRefName); + var protectedContexts = protectedBranches != null ? new HashSet(protectedBranches.RequiredStatusCheckContexts) : null; + var apiClient = await apiClientFactory.Create(address); var files = await apiClient.GetPullRequestFiles(owner, name, number).ToList(); - var lastCommitModel = await GetPullRequestLastCommitAdapter(address, owner, name, number); + var lastCommitModel = await GetPullRequestLastCommitAdapter(address, owner, name, number, protectedContexts); result.Statuses = (IReadOnlyList) lastCommitModel.Statuses ?? Array.Empty(); @@ -773,7 +779,8 @@ Task GetRepository(LocalRepositoryModel repository) return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath)); } - async Task GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number) + async Task GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, + int number, HashSet protectedContexts) { ICompiledQuery> query; if (address.IsGitHubDotCom()) @@ -867,8 +874,23 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres }; var connection = await graphqlFactory.CreateConnection(address); - var result = await connection.Run(query, vars); - return result.First(); + var results = await connection.Run(query, vars); + var result = results.First(); + + foreach (var resultCheckSuite in result.CheckSuites) + { + foreach (var checkRunModel in resultCheckSuite.CheckRuns) + { + checkRunModel.IsRequired = protectedContexts?.Contains(checkRunModel.Name) ?? false; + } + } + + foreach (var resultStatus in result.Statuses) + { + resultStatus.IsRequired = protectedContexts?.Contains(resultStatus.Context) ?? false; + } + + return result; } static void BuildPullRequestThreads(PullRequestDetailModel model) diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml index 2560e806af..caf2380d7f 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml @@ -47,6 +47,12 @@ + + + +