diff --git a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs index ec75bf27be..d28f198b7a 100644 --- a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs @@ -9,10 +9,14 @@ 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"; + public string DurationStatus { get; set; } + public PullRequestCheckStatus Status { get; set; } = PullRequestCheckStatus.Failure; public Uri DetailsUrl { get; set; } = new Uri("http://github.com"); diff --git a/src/GitHub.App/Services/RepositoryService.cs b/src/GitHub.App/Services/RepositoryService.cs index f713cabbb7..0d6e081342 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] @@ -25,6 +28,7 @@ public RepositoryService(IGraphQLClientFactory graphqlFactory) this.graphqlFactory = graphqlFactory; } + /// public async Task<(string owner, string name)?> FindParent(HostAddress address, string owner, string name) { Guard.ArgumentNotNull(address, nameof(address)); @@ -45,9 +49,49 @@ 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 a5cdc09851..e9ebb56b17 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reactive; @@ -9,6 +10,7 @@ using GitHub.Models; using GitHub.Primitives; using GitHub.Services; +using GitHub.UI.Converters; using ReactiveUI; namespace GitHub.ViewModels.GitHubPane @@ -49,6 +51,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; @@ -98,10 +101,18 @@ 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; - pullRequestCheckViewModel.Description = arg.checkRun.Summary; + + if (arg.checkRun.StartedAt.HasValue && arg.checkRun.CompletedAt.HasValue) + { + var timeSpanString = TimeSpanExtensions.Humanize(arg.checkRun.CompletedAt.Value - arg.checkRun.StartedAt.Value, CultureInfo.CurrentCulture, TimeSpanExtensions.OutputTense.Completed); + pullRequestCheckViewModel.DurationStatus = $"{checkStatus} - {timeSpanString}"; + } + + pullRequestCheckViewModel.Description = arg.checkRun.Title; pullRequestCheckViewModel.Status = checkStatus; pullRequestCheckViewModel.DetailsUrl = new Uri(arg.checkRun.DetailsUrl); return pullRequestCheckViewModel; @@ -136,9 +147,15 @@ private void DoOpenDetailsUrl() usageTracker.IncrementCounter(expression).Forget(); } + /// + public bool IsRequired { get; private set; } + /// public string Title { get; private set; } + /// + public string DurationStatus { get; private set; } + /// public string Description { get; private set; } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs index d24a9c7212..7e89d0b9f3 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs @@ -10,6 +10,11 @@ namespace GitHub.ViewModels.GitHubPane /// public interface IPullRequestCheckViewModel: IViewModel { + /// + /// The flag to show this Status/Check is required. + /// + bool IsRequired { get; } + /// /// The title of the Status/Check. /// @@ -30,6 +35,11 @@ public interface IPullRequestCheckViewModel: IViewModel /// Uri DetailsUrl { get; } + /// + /// The amount of time this Status/Check took to run. + /// + string DurationStatus { get; } + /// /// A command that opens the DetailsUrl in a browser. /// diff --git a/src/GitHub.Exports/Extensions/TimeSpanExtensions.cs b/src/GitHub.Exports/Extensions/TimeSpanExtensions.cs new file mode 100644 index 0000000000..7f1fbb09e5 --- /dev/null +++ b/src/GitHub.Exports/Extensions/TimeSpanExtensions.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; + +namespace GitHub.UI.Converters +{ + public static class TimeSpanExtensions + { + public static string Humanize(TimeSpan duration, CultureInfo culture, OutputTense outputTense = OutputTense.Past) + { + if (duration.Ticks <= 0) + { + return Resources.JustNow; + } + + const int year = 365; + const int month = 30; + const int day = 24; + const int hour = 60; + const int minute = 60; + + if (duration.TotalDays >= year) + { + return GetFormattedValue(culture, (int) (duration.TotalDays / year), + outputTense, + Resources.YearsAgo, Resources.Years, + Resources.YearAgo, Resources.Year); + } + + if (duration.TotalDays >= 360) + { + return string.Format(culture, outputTense == OutputTense.Past ? Resources.MonthsAgo : Resources.Month, 11); + } + + if (duration.TotalDays >= month) + { + return GetFormattedValue(culture, (int)(duration.TotalDays / month), + outputTense, + Resources.MonthsAgo, Resources.Months, + Resources.MonthAgo, Resources.Month); + } + + if (duration.TotalHours >= day) + { + return GetFormattedValue(culture, (int)(duration.TotalHours / day), + outputTense, + Resources.DaysAgo, Resources.Days, + Resources.DayAgo, Resources.Day); + } + + if (duration.TotalMinutes >= hour) + { + return GetFormattedValue(culture, (int)(duration.TotalMinutes / hour), + outputTense, + Resources.HoursAgo, Resources.Hours, + Resources.HourAgo, Resources.Hour); + } + + if (duration.TotalSeconds >= minute) + { + return GetFormattedValue(culture, (int)(duration.TotalSeconds / minute), + outputTense, + Resources.MinutesAgo, Resources.Minutes, + Resources.MinuteAgo, Resources.Minute); + } + + return GetFormattedValue(culture, (int) duration.TotalSeconds, + outputTense, + Resources.SecondsAgo, Resources.Seconds, + Resources.SecondAgo, Resources.Second); + } + + private static string GetFormattedValue(CultureInfo culture, int value, OutputTense outputTense, + string multiplePast, string multipleCompleted, + string singlePast, string singleCompleted) + { + var formatString = value > 1 + ? outputTense == OutputTense.Past ? multiplePast : multipleCompleted + : outputTense == OutputTense.Past ? singlePast : singleCompleted; + + return string.Format(culture, formatString, value); + } + + public enum OutputTense + { + Past, + Completed + } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 68b625c8ca..6da0d0c931 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -20,6 +20,7 @@ + diff --git a/src/GitHub.Exports/Models/CheckRunModel.cs b/src/GitHub.Exports/Models/CheckRunModel.cs index de6ec46d9d..41c8e112bc 100644 --- a/src/GitHub.Exports/Models/CheckRunModel.cs +++ b/src/GitHub.Exports/Models/CheckRunModel.cs @@ -23,6 +23,11 @@ public class CheckRunModel /// public CheckStatusState Status { get; set; } + /// + /// Identifies the date and time when the check run was started. + /// + public DateTimeOffset? StartedAt { get; set; } + /// /// Identifies the date and time when the check run was completed. /// @@ -43,11 +48,21 @@ public class CheckRunModel /// public string DetailsUrl { get; set; } + /// + /// The title of a Check Run. + /// + public string Title { get; set; } + /// /// The summary of a Check Run. /// public string Summary { get; set; } + /// + /// The flag to shows this Check Run is required. + /// + public bool IsRequired { get; set; } + /// /// The detail of a Check Run. /// 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..878e6e4183 100644 --- a/src/GitHub.Exports/Models/StatusModel.cs +++ b/src/GitHub.Exports/Models/StatusModel.cs @@ -24,5 +24,10 @@ public class StatusModel /// The descritption for the Status /// public string Description { get; set; } + + /// + /// The flag to shows this Status is required. + /// + 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..34bf4459cc 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,24 @@ public interface IRepositoryService /// otherwise null. /// Task<(string owner, string name)?> FindParent(HostAddress address, string owner, string name); + + /// + /// Gets the list of protected branches. + /// + /// The host address. + /// The repository owner. + /// The repository name. + /// + Task> GetProtectedBranches(HostAddress address, string owner, string name); + + /// + /// Gets the protected branch configuration of a particular branch, if any. + /// + /// The host address. + /// The repository owner. + /// The repository 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 e470647330..e8156fc426 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>(); @@ -399,15 +402,15 @@ public virtual async Task ReadPullRequestDetail(HostAddr var connection = await graphqlFactory.CreateConnection(address); var result = await connection.Run(readPullRequest, vars); - var apiClient = await apiClientFactory.Create(address); - - var files = await log.TimeAsync(nameof(apiClient.GetPullRequestFiles), - async () => await apiClient.GetPullRequestFiles(owner, name, number).ToList()); + 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 log.TimeAsync(nameof(GetPullRequestLastCommitAdapter), - () => GetPullRequestLastCommitAdapter(address, owner, name, number)); + () => GetPullRequestLastCommitAdapter(address, owner, name, number, protectedContexts)); - result.Statuses = (IReadOnlyList)lastCommitModel.Statuses ?? Array.Empty(); + result.Statuses = (IReadOnlyList) lastCommitModel.Statuses ?? Array.Empty(); if (lastCommitModel.CheckSuites == null) { @@ -815,7 +818,8 @@ Task GetRepository(LocalRepositoryModel repository) return Task.Run(() => 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()) @@ -838,8 +842,11 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres Conclusion = run.Conclusion.FromGraphQl(), Status = run.Status.FromGraphQl(), Name = run.Name, - DetailsUrl = run.Permalink, + Title = run.Title, Summary = run.Summary, + DetailsUrl = run.Permalink, + StartedAt = run.StartedAt, + CompletedAt = run.CompletedAt, Text = run.Text, Annotations = run.Annotations(null, null, null, null).AllPages() .Select(annotation => new CheckRunAnnotationModel @@ -906,8 +913,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.Resources/Resources.Designer.cs b/src/GitHub.Resources/Resources.Designer.cs index a22ae723f1..b659344d0f 100644 --- a/src/GitHub.Resources/Resources.Designer.cs +++ b/src/GitHub.Resources/Resources.Designer.cs @@ -531,21 +531,39 @@ public static string CreateTitle { } } + /// + /// Looks up a localized string similar to {0:N0} day. + /// + public static string Day { + get { + return ResourceManager.GetString("Day", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0:N0} day ago. /// - public static string day { + public static string DayAgo { + get { + return ResourceManager.GetString("DayAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} days. + /// + public static string Days { get { - return ResourceManager.GetString("day", resourceCulture); + return ResourceManager.GetString("Days", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} days ago. /// - public static string days { + public static string DaysAgo { get { - return ResourceManager.GetString("days", resourceCulture); + return ResourceManager.GetString("DaysAgo", resourceCulture); } } @@ -837,21 +855,39 @@ public static string GraphsNavigationItemText { } } + /// + /// Looks up a localized string similar to {0:N0} hour. + /// + public static string Hour { + get { + return ResourceManager.GetString("Hour", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0:N0} hour ago. /// - public static string hour { + public static string HourAgo { get { - return ResourceManager.GetString("hour", resourceCulture); + return ResourceManager.GetString("HourAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} hours. + /// + public static string Hours { + get { + return ResourceManager.GetString("Hours", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} hours ago. /// - public static string hours { + public static string HoursAgo { get { - return ResourceManager.GetString("hours", resourceCulture); + return ResourceManager.GetString("HoursAgo", resourceCulture); } } @@ -1053,39 +1089,75 @@ public static string makePrivateGist { } } + /// + /// Looks up a localized string similar to {0:N0} minute. + /// + public static string Minute { + get { + return ResourceManager.GetString("Minute", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0:N0} minute ago. /// - public static string minute { + public static string MinuteAgo { + get { + return ResourceManager.GetString("MinuteAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} minutes. + /// + public static string Minutes { get { - return ResourceManager.GetString("minute", resourceCulture); + return ResourceManager.GetString("Minutes", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} minutes ago. /// - public static string minutes { + public static string MinutesAgo { get { - return ResourceManager.GetString("minutes", resourceCulture); + return ResourceManager.GetString("MinutesAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} month. + /// + public static string Month { + get { + return ResourceManager.GetString("Month", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} month ago. /// - public static string month { + public static string MonthAgo { + get { + return ResourceManager.GetString("MonthAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} months. + /// + public static string Months { get { - return ResourceManager.GetString("month", resourceCulture); + return ResourceManager.GetString("Months", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} months ago. /// - public static string months { + public static string MonthsAgo { get { - return ResourceManager.GetString("months", resourceCulture); + return ResourceManager.GetString("MonthsAgo", resourceCulture); } } @@ -1908,21 +1980,39 @@ public static string SafeRepositoryNameWarning { } } + /// + /// Looks up a localized string similar to {0:N0} second. + /// + public static string Second { + get { + return ResourceManager.GetString("Second", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0:N0} second ago. /// - public static string second { + public static string SecondAgo { get { - return ResourceManager.GetString("second", resourceCulture); + return ResourceManager.GetString("SecondAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} seconds. + /// + public static string Seconds { + get { + return ResourceManager.GetString("Seconds", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} seconds ago. /// - public static string seconds { + public static string SecondsAgo { get { - return ResourceManager.GetString("seconds", resourceCulture); + return ResourceManager.GetString("SecondsAgo", resourceCulture); } } @@ -2297,21 +2387,39 @@ public static string Wrote { } } + /// + /// Looks up a localized string similar to {0:N0} year. + /// + public static string Year { + get { + return ResourceManager.GetString("Year", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0:N0} year ago. /// - public static string year { + public static string YearAgo { + get { + return ResourceManager.GetString("YearAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0:N0} years. + /// + public static string Years { get { - return ResourceManager.GetString("year", resourceCulture); + return ResourceManager.GetString("Years", resourceCulture); } } /// /// Looks up a localized string similar to {0:N0} years ago. /// - public static string years { + public static string YearsAgo { get { - return ResourceManager.GetString("years", resourceCulture); + return ResourceManager.GetString("YearsAgo", resourceCulture); } } diff --git a/src/GitHub.Resources/Resources.resx b/src/GitHub.Resources/Resources.resx index 1b909be5ee..2b87ae0086 100644 --- a/src/GitHub.Resources/Resources.resx +++ b/src/GitHub.Resources/Resources.resx @@ -336,45 +336,81 @@ https://git-scm.com/download/win Logout Required - + {0:N0} day ago - + {0:N0} days ago - + {0:N0} hour ago - + {0:N0} hours ago just now - + {0:N0} minute ago - + {0:N0} minutes ago - + {0:N0} month ago - + {0:N0} months ago - + {0:N0} second ago - + {0:N0} seconds ago - + {0:N0} year ago - + {0:N0} years ago + + {0:N0} day + + + {0:N0} days + + + {0:N0} hour + + + {0:N0} hours + + + {0:N0} minute + + + {0:N0} minutes + + + {0:N0} month + + + {0:N0} months + + + {0:N0} second + + + {0:N0} seconds + + + {0:N0} year + + + {0:N0} years + Invalid authentication code diff --git a/src/GitHub.Resources/Resources.zh-CN.resx b/src/GitHub.Resources/Resources.zh-CN.resx index 7d4aed8199..b61f39d450 100644 --- a/src/GitHub.Resources/Resources.zh-CN.resx +++ b/src/GitHub.Resources/Resources.zh-CN.resx @@ -336,45 +336,81 @@ https://git-scm.com/download/win 需要注销 - + {0:N0} 天前 - + {0:N0} 天前 - + {0:N0} 小时前 - + {0:N0} 小时前 此刻 - + {0:N0} 分钟前 - + {0:N0} 分钟前 - + {0:N0} 个月前 - + {0:N0} 个月前 - + {0:N0} 秒前 - + {0:N0} 秒前 - + {0:N0} 年前 - + {0:N0} 年前 + + {0:N0} day + + + {0:N0} days + + + {0:N0} hour + + + {0:N0} hours + + + {0:N0} minute + + + {0:N0} minutes + + + {0:N0} month + + + {0:N0} months + + + {0:N0} second + + + {0:N0} seconds + + + {0:N0} year + + + {0:N0} years + 验证码无效 diff --git a/src/GitHub.UI/Converters/DurationToStringConverter.cs b/src/GitHub.UI/Converters/DurationToStringConverter.cs index dbebe9f3fa..cc88249f7d 100644 --- a/src/GitHub.UI/Converters/DurationToStringConverter.cs +++ b/src/GitHub.UI/Converters/DurationToStringConverter.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using GitHub.UI.Converters; namespace GitHub.UI { @@ -17,30 +18,7 @@ public override object Convert(object value, Type targetType, object parameter, else return value; - if (duration.Ticks <= 0) - { - return Resources.JustNow; - } - - const int year = 365; - const int month = 30; - const int day = 24; - const int hour = 60; - const int minute = 60; - - if (duration.TotalDays >= year) - return string.Format(culture, (int)(duration.TotalDays / year) > 1 ? Resources.years : Resources.year, (int)(duration.TotalDays / year)); - else if (duration.TotalDays >= 360) - return string.Format(culture, Resources.months, 11); - else if (duration.TotalDays >= month) - return string.Format(culture, (int)(duration.TotalDays / (month)) > 1 ? Resources.months : Resources.month, (int)(duration.TotalDays / (month))); - else if (duration.TotalHours >= day) - return string.Format(culture, (int)(duration.TotalHours / day) > 1 ? Resources.days : Resources.day, (int)(duration.TotalHours / day)); - else if (duration.TotalMinutes >= hour) - return string.Format(culture, (int)(duration.TotalMinutes / hour) > 1 ? Resources.hours : Resources.hour, (int)(duration.TotalMinutes / hour)); - else if (duration.TotalSeconds >= minute) - return string.Format(culture, (int)(duration.TotalSeconds / minute) > 1 ? Resources.minutes : Resources.minute, (int)(duration.TotalSeconds / minute)); - return string.Format(culture, duration.TotalSeconds > 1 || duration.Ticks == 0 ? Resources.seconds : Resources.second, duration.TotalSeconds); + return TimeSpanExtensions.Humanize(duration, culture); } } } \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml index 8863e8ffb2..997154739a 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml @@ -67,5 +67,6 @@ - + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml index d47c6cbadc..371c39cb89 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml @@ -67,5 +67,6 @@ - + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml index 5ccf074b68..3a061e71f5 100644 --- a/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml @@ -67,5 +67,6 @@ - + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml index d67839c532..b1a150a750 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml @@ -22,37 +22,45 @@ - - - - - - - + + + + + + + + + + - - - + + + + + + Required + + - - - + + + + Checks + + - - - - - - + + Details + + + + + + diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs index 514b19927e..8d0736df38 100644 --- a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs @@ -295,6 +295,7 @@ static PullRequestSessionService CreateTarget(IDiffService diffService) diffService, Substitute.For(), Substitute.For(), + Substitute.For(), Substitute.For()); }