gh pr checks
A basic first pass on gh pr checks that shows all check runs for a given PR's latest commit.
This commit is contained in:
parent
bb65ca0635
commit
8dd93e1748
9 changed files with 645 additions and 5 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/internal/ghinstance"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
|
|
@ -72,12 +73,19 @@ type PullRequest struct {
|
|||
TotalCount int
|
||||
Nodes []struct {
|
||||
Commit struct {
|
||||
Oid string
|
||||
StatusCheckRollup struct {
|
||||
Contexts struct {
|
||||
Nodes []struct {
|
||||
State string
|
||||
Status string
|
||||
Conclusion string
|
||||
Name string
|
||||
Context string
|
||||
State string
|
||||
Status string
|
||||
Conclusion string
|
||||
StartedAt time.Time
|
||||
CompletedAt time.Time
|
||||
DetailsURL string
|
||||
TargetURL string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -272,9 +280,11 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
|
|||
contexts(last: 100) {
|
||||
nodes {
|
||||
...on StatusContext {
|
||||
context
|
||||
state
|
||||
}
|
||||
...on CheckRun {
|
||||
name
|
||||
status
|
||||
conclusion
|
||||
}
|
||||
|
|
@ -418,8 +428,32 @@ func PullRequestByNumber(client *Client, repo ghrepo.Interface, number int) (*Pu
|
|||
author {
|
||||
login
|
||||
}
|
||||
commits {
|
||||
commits(last: 1) {
|
||||
totalCount
|
||||
nodes {
|
||||
commit {
|
||||
oid
|
||||
statusCheckRollup {
|
||||
contexts(last: 100) {
|
||||
nodes {
|
||||
...on StatusContext {
|
||||
context
|
||||
state
|
||||
targetUrl
|
||||
}
|
||||
...on CheckRun {
|
||||
name
|
||||
status
|
||||
conclusion
|
||||
startedAt
|
||||
completedAt
|
||||
detailsUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
baseRefName
|
||||
headRefName
|
||||
|
|
@ -524,8 +558,32 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, baseBranch, hea
|
|||
author {
|
||||
login
|
||||
}
|
||||
commits {
|
||||
commits(last: 1) {
|
||||
totalCount
|
||||
nodes {
|
||||
commit {
|
||||
oid
|
||||
statusCheckRollup {
|
||||
contexts(last: 100) {
|
||||
nodes {
|
||||
...on StatusContext {
|
||||
context
|
||||
state
|
||||
targetUrl
|
||||
}
|
||||
...on CheckRun {
|
||||
name
|
||||
status
|
||||
conclusion
|
||||
startedAt
|
||||
completedAt
|
||||
detailsUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
url
|
||||
baseRefName
|
||||
|
|
|
|||
211
pkg/cmd/pr/checks/checks.go
Normal file
211
pkg/cmd/pr/checks/checks.go
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
package checks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ChecksOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Branch func() (string, error)
|
||||
Remotes func() (context.Remotes, error)
|
||||
|
||||
SelectorArg string
|
||||
}
|
||||
|
||||
func NewCmdChecks(f *cmdutil.Factory, runF func(*ChecksOptions) error) *cobra.Command {
|
||||
opts := &ChecksOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
Branch: f.Branch,
|
||||
Remotes: f.Remotes,
|
||||
BaseRepo: f.BaseRepo,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "checks",
|
||||
Short: "Show CI status for a single pull request",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// support `-R, --repo` override
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
|
||||
if repoOverride, _ := cmd.Flags().GetString("repo"); repoOverride != "" && len(args) == 0 {
|
||||
return &cmdutil.FlagError{Err: errors.New("argument required when using the --repo flag")}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
opts.SelectorArg = args[0]
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return checksRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func checksRun(opts *ChecksOptions) error {
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
pr, _, err := shared.PRFromArgs(apiClient, opts.BaseRepo, opts.Branch, opts.Remotes, opts.SelectorArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pr.Commits.Nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rollup := pr.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.Nodes
|
||||
if len(rollup) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
passing := 0
|
||||
failing := 0
|
||||
pending := 0
|
||||
|
||||
type output struct {
|
||||
mark string
|
||||
bucket string
|
||||
name string
|
||||
elapsed string
|
||||
link string
|
||||
}
|
||||
|
||||
outputs := []output{}
|
||||
|
||||
for _, c := range pr.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.Nodes {
|
||||
mark := ""
|
||||
bucket := ""
|
||||
state := c.State
|
||||
if state == "" {
|
||||
if c.Status == "COMPLETED" {
|
||||
state = c.Conclusion
|
||||
} else {
|
||||
state = c.Status
|
||||
}
|
||||
}
|
||||
switch state {
|
||||
case "SUCCESS", "NEUTRAL", "SKIPPED":
|
||||
mark = utils.GreenCheck()
|
||||
passing++
|
||||
bucket = "pass"
|
||||
case "ERROR", "FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED":
|
||||
mark = utils.RedX()
|
||||
failing++
|
||||
bucket = "fail"
|
||||
case "EXPECTED", "REQUESTED", "QUEUED", "PENDING", "IN_PROGRESS", "STALE":
|
||||
mark = utils.YellowDash()
|
||||
pending++
|
||||
bucket = "pending"
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported status: %q", state))
|
||||
}
|
||||
|
||||
elapsed := ""
|
||||
zeroTime := time.Time{}
|
||||
|
||||
if c.StartedAt != zeroTime && c.CompletedAt != zeroTime {
|
||||
e := c.CompletedAt.Sub(c.StartedAt)
|
||||
if e > 0 {
|
||||
elapsed = e.String()
|
||||
}
|
||||
}
|
||||
|
||||
link := c.DetailsURL
|
||||
if link == "" {
|
||||
link = c.TargetURL
|
||||
}
|
||||
|
||||
name := c.Name
|
||||
if name == "" {
|
||||
name = c.Context
|
||||
}
|
||||
|
||||
outputs = append(outputs, output{mark, bucket, name, elapsed, link})
|
||||
}
|
||||
|
||||
sort.Slice(outputs, func(i, j int) bool {
|
||||
if outputs[i].bucket == outputs[j].bucket {
|
||||
return outputs[i].name < outputs[j].name
|
||||
} else {
|
||||
if outputs[i].bucket == "fail" {
|
||||
return true
|
||||
} else if outputs[i].bucket == "pending" && outputs[j].bucket == "success" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
tp := utils.NewTablePrinter(opts.IO)
|
||||
|
||||
for _, o := range outputs {
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
tp.AddField(o.mark, nil, nil)
|
||||
tp.AddField(o.name, nil, nil)
|
||||
tp.AddField(o.elapsed, nil, nil)
|
||||
tp.AddField(o.link, nil, nil)
|
||||
} else {
|
||||
tp.AddField(o.name, nil, nil)
|
||||
tp.AddField(o.bucket, nil, nil)
|
||||
if o.elapsed == "" {
|
||||
tp.AddField("0", nil, nil)
|
||||
} else {
|
||||
tp.AddField(o.elapsed, nil, nil)
|
||||
}
|
||||
tp.AddField(o.link, nil, nil)
|
||||
}
|
||||
|
||||
tp.EndRow()
|
||||
}
|
||||
|
||||
summary := ""
|
||||
if failing+passing+pending > 0 {
|
||||
if failing > 0 {
|
||||
summary = "Some checks were not successful"
|
||||
} else if pending > 0 {
|
||||
summary = "Some checks are still pending"
|
||||
} else {
|
||||
summary = "All checks were successful"
|
||||
}
|
||||
|
||||
tallies := fmt.Sprintf(
|
||||
"%d failing, %d successful, and %d pending checks",
|
||||
failing, passing, pending)
|
||||
|
||||
summary = fmt.Sprintf("%s\n%s", utils.Bold(summary), tallies)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintln(opts.IO.Out, summary)
|
||||
fmt.Fprintln(opts.IO.Out)
|
||||
}
|
||||
|
||||
return tp.Render()
|
||||
}
|
||||
200
pkg/cmd/pr/checks/checks_test.go
Normal file
200
pkg/cmd/pr/checks/checks_test.go
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
package checks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCmdChecks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants ChecksOptions
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
cli: "",
|
||||
wants: ChecksOptions{},
|
||||
},
|
||||
{
|
||||
name: "pr argument",
|
||||
cli: "1234",
|
||||
wants: ChecksOptions{
|
||||
SelectorArg: "1234",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *ChecksOptions
|
||||
cmd := NewCmdChecks(f, func(opts *ChecksOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.SelectorArg, gotOpts.SelectorArg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checksRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
stubs func(*httpmock.Registry)
|
||||
wantOut string
|
||||
nontty bool
|
||||
}{
|
||||
{
|
||||
name: "no commits",
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
||||
httpmock.JSONResponse(
|
||||
bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"pullRequest": { "number": 123 }
|
||||
} } }
|
||||
`)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no checks",
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"pullRequest": { "number": 123, "commits": { "nodes": [{"commit": {"oid": "abc"}}]} }
|
||||
} } }
|
||||
`))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some failing",
|
||||
fixture: "./fixtures/someFailing.json",
|
||||
wantOut: "Some checks were not successful\n1 failing, 1 successful, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n- slow tests 1m26s sweet link\n",
|
||||
},
|
||||
{
|
||||
name: "some pending",
|
||||
fixture: "./fixtures/somePending.json",
|
||||
wantOut: "Some checks are still pending\n0 failing, 2 successful, and 1 pending checks\n\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n- slow tests 1m26s sweet link\n",
|
||||
},
|
||||
{
|
||||
name: "all passing",
|
||||
fixture: "./fixtures/allPassing.json",
|
||||
wantOut: "All checks were successful\n0 failing, 3 successful, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n",
|
||||
},
|
||||
{
|
||||
name: "with statuses",
|
||||
fixture: "./fixtures/withStatuses.json",
|
||||
wantOut: "Some checks were not successful\n1 failing, 2 successful, and 0 pending checks\n\nX a status sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n",
|
||||
},
|
||||
{
|
||||
name: "no commits",
|
||||
nontty: true,
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query PullRequestByNumber\b`),
|
||||
httpmock.JSONResponse(
|
||||
bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"pullRequest": { "number": 123 }
|
||||
} } }
|
||||
`)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no checks",
|
||||
nontty: true,
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"pullRequest": { "number": 123, "commits": { "nodes": [{"commit": {"oid": "abc"}}]} }
|
||||
} } }
|
||||
`))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some failing",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/someFailing.json",
|
||||
wantOut: "sad tests\tfail\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n",
|
||||
},
|
||||
{
|
||||
name: "some pending",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/somePending.json",
|
||||
wantOut: "cool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n",
|
||||
},
|
||||
{
|
||||
name: "all passing",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/allPassing.json",
|
||||
wantOut: "awesome tests\tpass\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n",
|
||||
},
|
||||
{
|
||||
name: "with statuses",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/withStatuses.json",
|
||||
wantOut: "a status\tfail\t0\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, stdout, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(!tt.nontty)
|
||||
|
||||
opts := &ChecksOptions{
|
||||
IO: io,
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
},
|
||||
SelectorArg: "123",
|
||||
}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
if tt.stubs != nil {
|
||||
tt.stubs(reg)
|
||||
} else if tt.fixture != "" {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query PullRequestByNumber\b`), httpmock.FileResponse(tt.fixture))
|
||||
} else {
|
||||
panic("need either stubs or fixture key")
|
||||
}
|
||||
|
||||
opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
|
||||
err := checksRun(opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wantOut, stdout.String())
|
||||
reg.Verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
41
pkg/cmd/pr/checks/fixtures/allPassing.json
Normal file
41
pkg/cmd/pr/checks/fixtures/allPassing.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{ "data": { "repository": { "pullRequest": {
|
||||
"number": 123,
|
||||
"commits": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
} } } }
|
||||
41
pkg/cmd/pr/checks/fixtures/someFailing.json
Normal file
41
pkg/cmd/pr/checks/fixtures/someFailing.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{ "data": { "repository": { "pullRequest": {
|
||||
"number": 123,
|
||||
"commits": {
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"conclusion": "",
|
||||
"status": "IN_PROGRESS",
|
||||
"name": "slow tests",
|
||||
"completedAt": "2020-08-27T19:00:12Z",
|
||||
"startedAt": "2020-08-27T18:58:46Z",
|
||||
"detailsUrl": "sweet link"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
} } } }
|
||||
41
pkg/cmd/pr/checks/fixtures/somePending.json
Normal file
41
pkg/cmd/pr/checks/fixtures/somePending.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{ "data": { "repository": { "pullRequest": {
|
||||
"number": 123,
|
||||
"commits": {
|
||||
"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": "",
|
||||
"status": "IN_PROGRESS",
|
||||
"name": "slow tests",
|
||||
"completedAt": "2020-08-27T19:00:12Z",
|
||||
"startedAt": "2020-08-27T18:58:46Z",
|
||||
"detailsUrl": "sweet link"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
} } } }
|
||||
38
pkg/cmd/pr/checks/fixtures/withStatuses.json
Normal file
38
pkg/cmd/pr/checks/fixtures/withStatuses.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{ "data": { "repository": { "pullRequest": {
|
||||
"number": 123,
|
||||
"commits": {
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"state": "FAILURE",
|
||||
"name": "a status",
|
||||
"targetUrl": "sweet link"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
} } } }
|
||||
|
|
@ -3,6 +3,7 @@ package pr
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
cmdCheckout "github.com/cli/cli/pkg/cmd/pr/checkout"
|
||||
cmdChecks "github.com/cli/cli/pkg/cmd/pr/checks"
|
||||
cmdClose "github.com/cli/cli/pkg/cmd/pr/close"
|
||||
cmdCreate "github.com/cli/cli/pkg/cmd/pr/create"
|
||||
cmdDiff "github.com/cli/cli/pkg/cmd/pr/diff"
|
||||
|
|
@ -51,6 +52,7 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(cmdReview.NewCmdReview(f, nil))
|
||||
cmd.AddCommand(cmdStatus.NewCmdStatus(f, nil))
|
||||
cmd.AddCommand(cmdView.NewCmdView(f, nil))
|
||||
cmd.AddCommand(cmdChecks.NewCmdChecks(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,3 +118,11 @@ func DisplayURL(urlStr string) string {
|
|||
func GreenCheck() string {
|
||||
return Green("✓")
|
||||
}
|
||||
|
||||
func YellowDash() string {
|
||||
return Yellow("-")
|
||||
}
|
||||
|
||||
func RedX() string {
|
||||
return Red("X")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue