diff --git a/api/queries_pr.go b/api/queries_pr.go index f507a4e25..e4f6013fa 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -269,6 +269,7 @@ func (pr *PullRequest) ChecksStatus() (summary PullRequestChecksStatus) { } summary.Total++ } + return } diff --git a/pkg/cmd/pr/shared/display.go b/pkg/cmd/pr/shared/display.go index e31c86d2a..7bd306104 100644 --- a/pkg/cmd/pr/shared/display.go +++ b/pkg/cmd/pr/shared/display.go @@ -73,3 +73,21 @@ func ListHeader(repoName string, itemName string, matchCount int, totalMatchCoun return fmt.Sprintf("Showing %d of %s in %s", matchCount, text.Pluralize(totalMatchCount, fmt.Sprintf("open %s", itemName)), repoName) } + +func PrCheckStatusSummaryWithColor(cs *iostreams.ColorScheme, checks api.PullRequestChecksStatus) string { + var summary = cs.Gray("No checks") + if checks.Total > 0 { + if checks.Failing > 0 { + if checks.Failing == checks.Total { + summary = cs.Red("× All checks failing") + } else { + summary = cs.Redf("× %d/%d checks failing", checks.Failing, checks.Total) + } + } else if checks.Pending > 0 { + summary = cs.Yellow("- Checks pending") + } else if checks.Passing == checks.Total { + summary = cs.Green("✓ Checks passing") + } + } + return summary +} diff --git a/pkg/cmd/pr/shared/display_test.go b/pkg/cmd/pr/shared/display_test.go index ae2a1f680..89fdef129 100644 --- a/pkg/cmd/pr/shared/display_test.go +++ b/pkg/cmd/pr/shared/display_test.go @@ -1,6 +1,12 @@ package shared -import "testing" +import ( + "testing" + + "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/stretchr/testify/assert" +) func Test_listHeader(t *testing.T) { type args struct { @@ -90,3 +96,73 @@ func Test_listHeader(t *testing.T) { }) } } + +func TestPrCheckStatusSummaryWithColor(t *testing.T) { + testCases := []struct { + Name string + args api.PullRequestChecksStatus + want string + }{ + { + Name: "No Checks", + args: api.PullRequestChecksStatus{ + Total: 0, + Failing: 0, + Passing: 0, + Pending: 0, + }, + want: "No checks", + }, + { + Name: "All Passing", + args: api.PullRequestChecksStatus{ + Total: 3, + Failing: 0, + Passing: 3, + Pending: 0, + }, + want: "✓ Checks passing", + }, + { + Name: "Some pending", + args: api.PullRequestChecksStatus{ + Total: 3, + Failing: 0, + Passing: 1, + Pending: 2, + }, + want: "- Checks pending", + }, + { + Name: "Sll failing", + args: api.PullRequestChecksStatus{ + Total: 3, + Failing: 3, + Passing: 0, + Pending: 0, + }, + want: "× All checks failing", + }, + { + Name: "Some failing", + args: api.PullRequestChecksStatus{ + Total: 3, + Failing: 2, + Passing: 1, + Pending: 0, + }, + want: "× 2/3 checks failing", + }, + } + + ios, _, _, _ := iostreams.Test() + ios.SetStdoutTTY(true) + ios.SetAlternateScreenBufferEnabled(true) + cs := ios.ColorScheme() + + for _, testCase := range testCases { + out := PrCheckStatusSummaryWithColor(cs, testCase.args) + assert.Equal(t, testCase.want, out) + } + +} diff --git a/pkg/cmd/pr/status/status.go b/pkg/cmd/pr/status/status.go index ba070908d..784159564 100644 --- a/pkg/cmd/pr/status/status.go +++ b/pkg/cmd/pr/status/status.go @@ -234,18 +234,7 @@ func printPrs(io *iostreams.IOStreams, totalCount int, prs ...api.PullRequest) { } if checks.Total > 0 { - var summary string - if checks.Failing > 0 { - if checks.Failing == checks.Total { - summary = cs.Red("× All checks failing") - } else { - summary = cs.Redf("× %d/%d checks failing", checks.Failing, checks.Total) - } - } else if checks.Pending > 0 { - summary = cs.Yellow("- Checks pending") - } else if checks.Passing == checks.Total { - summary = cs.Green("✓ Checks passing") - } + summary := shared.PrCheckStatusSummaryWithColor(cs, checks) fmt.Fprint(w, summary) } diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksFailing.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksFailing.json new file mode 100644 index 000000000..59c4acd8a --- /dev/null +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksFailing.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "state": "OPEN", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "additions": 100, + "deletions": 10, + "assignees": { + "nodes": [], + "totalcount": 0 + }, + "labels": { + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true, + "isDraft": false, + "statusCheckRollup": { + "nodes": [ + { + "commit": { + "oid": "abc", + "statusCheckRollup": { + "contexts": { + "nodes": [ + { + "conclusion": "FAILURE", + "status": "COMPLETED", + "name": "cool tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + }, + { + "conclusion": "FAILURE", + "status": "COMPLETED", + "name": "sad tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksPassing.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksPassing.json new file mode 100644 index 000000000..d55e460b3 --- /dev/null +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithAllChecksPassing.json @@ -0,0 +1,82 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "state": "OPEN", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "additions": 100, + "deletions": 10, + "assignees": { + "nodes": [], + "totalcount": 0 + }, + "labels": { + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true, + "isDraft": false, + "statusCheckRollup": { + "nodes": [ + { + "commit": { + "oid": "abc", + "statusCheckRollup": { + "contexts": { + "nodes": [ + { + "conclusion": "SUCCESS", + "status": "COMPLETED", + "name": "cool tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + }, + { + "conclusion": "SUCCESS", + "status": "COMPLETED", + "name": "rad tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + }, + { + "conclusion": "SUCCESS", + "status": "COMPLETED", + "name": "awesome tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithNoChecks.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithNoChecks.json new file mode 100644 index 000000000..563aa6c6b --- /dev/null +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithNoChecks.json @@ -0,0 +1,58 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "state": "OPEN", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "additions": 100, + "deletions": 10, + "assignees": { + "nodes": [], + "totalcount": 0 + }, + "labels": { + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true, + "isDraft": false, + "statusCheckRollup": { + "nodes": [ + { + "commit": { + "oid": "abc", + "statusCheckRollup": { + "contexts": { + "nodes": [ + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksFailing.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksFailing.json new file mode 100644 index 000000000..d817a5da0 --- /dev/null +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksFailing.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "state": "OPEN", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "additions": 100, + "deletions": 10, + "assignees": { + "nodes": [], + "totalcount": 0 + }, + "labels": { + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true, + "isDraft": false, + "statusCheckRollup": { + "nodes": [ + { + "commit": { + "oid": "abc", + "statusCheckRollup": { + "contexts": { + "nodes": [ + { + "conclusion": "SUCCESS", + "status": "COMPLETED", + "name": "cool tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + }, + { + "conclusion": "FAILURE", + "status": "COMPLETED", + "name": "sad tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksPending.json b/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksPending.json new file mode 100644 index 000000000..019186783 --- /dev/null +++ b/pkg/cmd/pr/view/fixtures/prViewPreviewWithSomeChecksPending.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "number": 12, + "title": "Blueberries are from a fork", + "state": "OPEN", + "body": "**blueberries taste good**", + "url": "https://github.com/OWNER/REPO/pull/12", + "author": { + "login": "nobody" + }, + "additions": 100, + "deletions": 10, + "assignees": { + "nodes": [], + "totalcount": 0 + }, + "labels": { + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" + }, + "commits": { + "totalCount": 12 + }, + "baseRefName": "master", + "headRefName": "blueberries", + "headRepositoryOwner": { + "login": "hubot" + }, + "isCrossRepository": true, + "isDraft": false, + "statusCheckRollup": { + "nodes": [ + { + "commit": { + "oid": "abc", + "statusCheckRollup": { + "contexts": { + "nodes": [ + { + "conclusion": "", + "status": "WAITING", + "name": "cool tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + }, + { + "conclusion": "SUCCESS", + "status": "COMPLETED", + "name": "sad tests", + "completedAt": "2020-08-27T19:00:12Z", + "startedAt": "2020-08-27T18:58:46Z", + "detailsUrl": "sweet link" + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/pkg/cmd/pr/view/view.go b/pkg/cmd/pr/view/view.go index a055922a8..10300a23c 100644 --- a/pkg/cmd/pr/view/view.go +++ b/pkg/cmd/pr/view/view.go @@ -81,7 +81,7 @@ var defaultFields = []string{ "isDraft", "maintainerCanModify", "mergeable", "additions", "deletions", "commitsCount", "baseRefName", "headRefName", "headRepositoryOwner", "headRepository", "isCrossRepository", "reviewRequests", "reviews", "assignees", "labels", "projectCards", "milestone", - "comments", "reactionGroups", "createdAt", + "comments", "reactionGroups", "createdAt", "statusCheckRollup", } func viewRun(opts *ViewOptions) error { @@ -171,17 +171,30 @@ func printHumanPrPreview(opts *ViewOptions, pr *api.PullRequest) error { // Header (Title and State) fmt.Fprintf(out, "%s #%d\n", cs.Bold(pr.Title), pr.Number) fmt.Fprintf(out, - "%s • %s wants to merge %s into %s from %s • %s • %s %s \n", + "%s • %s wants to merge %s into %s from %s • %s\n", shared.StateTitleWithColor(cs, *pr), pr.Author.Login, text.Pluralize(pr.Commits.TotalCount, "commit"), pr.BaseRefName, pr.HeadRefName, text.FuzzyAgo(opts.Now(), pr.CreatedAt), + ) + + // added/removed + fmt.Fprintf(out, + "%s %s", cs.Green("+"+strconv.Itoa(pr.Additions)), cs.Red("-"+strconv.Itoa(pr.Deletions)), ) + // checks + checks := pr.ChecksStatus() + if summary := shared.PrCheckStatusSummaryWithColor(cs, checks); summary != "" { + fmt.Fprintf(out, " • %s\n", summary) + } else { + fmt.Fprintln(out) + } + // Reactions if reactions := shared.ReactionGroupList(pr.ReactionGroups); reactions != "" { fmt.Fprint(out, reactions) diff --git a/pkg/cmd/pr/view/view_test.go b/pkg/cmd/pr/view/view_test.go index 755e07542..166289050 100644 --- a/pkg/cmd/pr/view/view_test.go +++ b/pkg/cmd/pr/view/view_test.go @@ -353,7 +353,8 @@ func TestPRView_Preview(t *testing.T) { }, expectedOutputs: []string{ `Blueberries are from a fork #12`, - `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago.+100.-10`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, @@ -366,7 +367,8 @@ func TestPRView_Preview(t *testing.T) { }, expectedOutputs: []string{ `Blueberries are from a fork #12`, - `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago.+100.-10`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10`, `Reviewers:.*1 \(.*Requested.*\)\n`, `Assignees:.*marseilles, monaco\n`, `Labels:.*one, two, three, four, five\n`, @@ -398,7 +400,8 @@ func TestPRView_Preview(t *testing.T) { }, expectedOutputs: []string{ `Blueberries are from a fork #12`, - `Closed.*nobody wants to merge 12 commits into master from blueberries . about X years ago.+100.-10`, + `Closed.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, @@ -411,7 +414,8 @@ func TestPRView_Preview(t *testing.T) { }, expectedOutputs: []string{ `Blueberries are from a fork #12`, - `Merged.*nobody wants to merge 12 commits into master from blueberries . about X years ago.+100.-10`, + `Merged.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, }, @@ -424,7 +428,78 @@ func TestPRView_Preview(t *testing.T) { }, expectedOutputs: []string{ `Blueberries are from a fork #12`, - `Draft.*nobody wants to merge 12 commits into master from blueberries . about X years ago.+100.-10`, + `Draft.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10`, + `blueberries taste good`, + `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, + }, + }, + "Open PR with all checks passing": { + branch: "master", + args: "12", + fixtures: map[string]string{ + "PullRequestByNumber": "./fixtures/prViewPreviewWithAllChecksPassing.json", + }, + expectedOutputs: []string{ + `Blueberries are from a fork #12`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10 • ✓ Checks passing`, + `blueberries taste good`, + `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, + }, + }, + "Open PR with all checks failing": { + branch: "master", + args: "12", + fixtures: map[string]string{ + "PullRequestByNumber": "./fixtures/prViewPreviewWithAllChecksFailing.json", + }, + expectedOutputs: []string{ + `Blueberries are from a fork #12`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10 • × All checks failing`, + `blueberries taste good`, + `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, + }, + }, + "Open PR with some checks failing": { + branch: "master", + args: "12", + fixtures: map[string]string{ + "PullRequestByNumber": "./fixtures/prViewPreviewWithSomeChecksFailing.json", + }, + expectedOutputs: []string{ + `Blueberries are from a fork #12`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10 • × 1/2 checks failing`, + `blueberries taste good`, + `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, + }, + }, + "Open PR with some checks pending": { + branch: "master", + args: "12", + fixtures: map[string]string{ + "PullRequestByNumber": "./fixtures/prViewPreviewWithSomeChecksPending.json", + }, + expectedOutputs: []string{ + `Blueberries are from a fork #12`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10 • - Checks pending`, + `blueberries taste good`, + `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, + }, + }, + "Open PR with no checks": { + branch: "master", + args: "12", + fixtures: map[string]string{ + "PullRequestByNumber": "./fixtures/prViewPreviewWithNoChecks.json", + }, + expectedOutputs: []string{ + `Blueberries are from a fork #12`, + `Open.*nobody wants to merge 12 commits into master from blueberries . about X years ago`, + `.+100.-10 • No checks`, `blueberries taste good`, `View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12`, },