Merge pull request #5069 from hirasawayuki/fix-pr-checks-cmd

Fix duplicates check results returned by `gh pr checks`
This commit is contained in:
Nate Smith 2022-01-26 16:46:32 -06:00 committed by GitHub
commit 3a088e4df5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 253 additions and 14 deletions

View file

@ -80,18 +80,7 @@ type PullRequest struct {
Commit struct {
StatusCheckRollup struct {
Contexts struct {
Nodes []struct {
TypeName string `json:"__typename"`
Name string `json:"name"`
Context string `json:"context,omitempty"`
State string `json:"state,omitempty"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
StartedAt time.Time `json:"startedAt"`
CompletedAt time.Time `json:"completedAt"`
DetailsURL string `json:"detailsUrl"`
TargetURL string `json:"targetUrl,omitempty"`
}
Nodes []CheckContext
PageInfo struct {
HasNextPage bool
EndCursor string
@ -113,6 +102,19 @@ type PullRequest struct {
ReviewRequests ReviewRequests
}
type CheckContext struct {
TypeName string `json:"__typename"`
Name string `json:"name"`
Context string `json:"context,omitempty"`
State string `json:"state,omitempty"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
StartedAt time.Time `json:"startedAt"`
CompletedAt time.Time `json:"completedAt"`
DetailsURL string `json:"detailsUrl"`
TargetURL string `json:"targetUrl,omitempty"`
}
type PRRepository struct {
ID string `json:"id"`
Name string `json:"name"`

View file

@ -6,6 +6,7 @@ import (
"time"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
@ -41,7 +42,7 @@ func NewCmdChecks(f *cmdutil.Factory, runF func(*ChecksOptions) error) *cobra.Co
Show CI status for a single pull request.
Without an argument, the pull request that belongs to the current branch
is selected.
is selected.
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
@ -118,7 +119,8 @@ func checksRun(opts *ChecksOptions) error {
outputs := []output{}
for _, c := range pr.StatusCheckRollup.Nodes[0].Commit.StatusCheckRollup.Contexts.Nodes {
checkContexts := pr.StatusCheckRollup.Nodes[0].Commit.StatusCheckRollup.Contexts.Nodes
for _, c := range eliminateDuplicates(checkContexts) {
mark := "✓"
bucket := "pass"
state := c.State
@ -245,3 +247,26 @@ func checksRun(opts *ChecksOptions) error {
return nil
}
func eliminateDuplicates(checkContexts []api.CheckContext) []api.CheckContext {
// To return the most recent check, sort in descending order by StartedAt.
sort.Slice(checkContexts, func(i, j int) bool { return checkContexts[i].StartedAt.After(checkContexts[j].StartedAt) })
m := make(map[string]struct{})
unique := make([]api.CheckContext, 0, len(checkContexts))
// Eliminate duplicates using Name or Context.
for _, ctx := range checkContexts {
key := ctx.Name
if key == "" {
key = ctx.Context
}
if _, ok := m[key]; ok {
continue
}
unique = append(unique, ctx)
m[key] = struct{}{}
}
return unique
}

View file

@ -5,7 +5,9 @@ import (
"encoding/json"
"io"
"os"
"reflect"
"testing"
"time"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
@ -239,3 +241,213 @@ func TestChecksRun_web(t *testing.T) {
})
}
}
func TestEliminateDupulicates(t *testing.T) {
tests := []struct {
name string
checkContexts []api.CheckContext
want []api.CheckContext
}{
{
name: "duplicate CheckRun (lint)",
checkContexts: []api.CheckContext{
{
TypeName: "CheckRun",
Name: "build (ubuntu-latest)",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/1",
},
{
TypeName: "CheckRun",
Name: "lint",
Status: "COMPLETED",
Conclusion: "FAILURE",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/2",
},
{
TypeName: "CheckRun",
Name: "lint",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/3",
},
},
want: []api.CheckContext{
{
TypeName: "CheckRun",
Name: "lint",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/3",
},
{
TypeName: "CheckRun",
Name: "build (ubuntu-latest)",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/1",
},
},
},
{
name: "duplicate StatusContext (Windows GPU)",
checkContexts: []api.CheckContext{
{
TypeName: "StatusContext",
Name: "",
Context: "Windows GPU",
State: "FAILURE",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/2",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Windows GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/3",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Linux GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/1",
},
},
want: []api.CheckContext{
{
TypeName: "StatusContext",
Name: "",
Context: "Windows GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
CompletedAt: time.Date(2022, 2, 2, 2, 2, 2, 2, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/3",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Linux GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/1",
},
},
},
{
name: "unique CheckContext",
checkContexts: []api.CheckContext{
{
TypeName: "CheckRun",
Name: "build (ubuntu-latest)",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/1",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Windows GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/2",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Linux GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/3",
},
},
want: []api.CheckContext{
{
TypeName: "CheckRun",
Name: "build (ubuntu-latest)",
Status: "COMPLETED",
Conclusion: "SUCCESS",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "https://github.com/cli/cli/runs/1",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Windows GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/2",
},
{
TypeName: "StatusContext",
Name: "",
Context: "Linux GPU",
State: "SUCCESS",
Status: "",
Conclusion: "",
StartedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
CompletedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
DetailsURL: "",
TargetURL: "https://github.com/cli/cli/3",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := eliminateDuplicates(tt.checkContexts)
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("got eliminateDuplicates %+v, want %+v\n", got, tt.want)
}
})
}
}