From b0fd9c6f045be883b26e234327413601a42a5c30 Mon Sep 17 00:00:00 2001 From: hirasawayuki Date: Fri, 21 Jan 2022 00:02:42 +0900 Subject: [PATCH] Fix duplicates check results returned by `gh pr checks` --- api/queries_pr.go | 26 ++-- pkg/cmd/pr/checks/checks.go | 29 ++++- pkg/cmd/pr/checks/checks_test.go | 212 +++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+), 14 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 300846c23..9f949dc35 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -79,18 +79,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 @@ -111,6 +100,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"` diff --git a/pkg/cmd/pr/checks/checks.go b/pkg/cmd/pr/checks/checks.go index 516c06ca7..011856385 100644 --- a/pkg/cmd/pr/checks/checks.go +++ b/pkg/cmd/pr/checks/checks.go @@ -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 +} diff --git a/pkg/cmd/pr/checks/checks_test.go b/pkg/cmd/pr/checks/checks_test.go index 78d664c4f..96d000624 100644 --- a/pkg/cmd/pr/checks/checks_test.go +++ b/pkg/cmd/pr/checks/checks_test.go @@ -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) + } + }) + } +}