diff --git a/pkg/cmd/project/field-list/field_list.go b/pkg/cmd/project/field-list/field_list.go index 3e36e2a86..f15369c94 100644 --- a/pkg/cmd/project/field-list/field_list.go +++ b/pkg/cmd/project/field-list/field_list.go @@ -23,7 +23,6 @@ type listOpts struct { type listConfig struct { io *iostreams.IOStreams - tp *tableprinter.TablePrinter client *queries.Client opts listOpts } @@ -52,10 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C opts.number = int32(num) } - t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Name", "Data type", "ID")) config := listConfig{ io: f.IOStreams, - tp: t, client: client, opts: opts, } @@ -109,14 +106,16 @@ func printResults(config listConfig, fields []queries.ProjectField, login string return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no fields", config.opts.number, login)) } + tp := tableprinter.New(config.io, tableprinter.WithHeader("Name", "Data type", "ID")) + for _, f := range fields { - config.tp.AddField(f.Name()) - config.tp.AddField(f.Type()) - config.tp.AddField(f.ID(), tableprinter.WithTruncate(nil)) - config.tp.EndRow() + tp.AddField(f.Name()) + tp.AddField(f.Type()) + tp.AddField(f.ID(), tableprinter.WithTruncate(nil)) + tp.EndRow() } - return config.tp.Render() + return tp.Render() } func printJSON(config listConfig, project *queries.Project) error { diff --git a/pkg/cmd/project/field-list/field_list_test.go b/pkg/cmd/project/field-list/field_list_test.go index 85e67451f..ca01599fb 100644 --- a/pkg/cmd/project/field-list/field_list_test.go +++ b/pkg/cmd/project/field-list/field_list_test.go @@ -3,7 +3,7 @@ package fieldlist import ( "testing" - "github.com/cli/cli/v2/internal/tableprinter" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/pkg/cmd/project/shared/queries" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -87,6 +87,101 @@ func TestNewCmdList(t *testing.T) { } } +func TestRunList_User_tty(t *testing.T) { + defer gock.Off() + // gock.Observe(gock.DumpRequest) + + // get user ID + gock.New("https://api.github.com"). + Post("/graphql"). + MatchType("json"). + JSON(map[string]interface{}{ + "query": "query UserOrgOwner.*", + "variables": map[string]interface{}{ + "login": "monalisa", + }, + }). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "id": "an ID", + }, + }, + "errors": []interface{}{ + map[string]interface{}{ + "type": "NOT_FOUND", + "path": []string{"organization"}, + }, + }, + }) + + // list project fields + gock.New("https://api.github.com"). + Post("/graphql"). + JSON(map[string]interface{}{ + "query": "query UserProject.*", + "variables": map[string]interface{}{ + "login": "monalisa", + "number": 1, + "firstItems": queries.LimitMax, + "afterItems": nil, + "firstFields": queries.LimitDefault, + "afterFields": nil, + }, + }). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "projectV2": map[string]interface{}{ + "fields": map[string]interface{}{ + "nodes": []map[string]interface{}{ + { + "__typename": "ProjectV2Field", + "name": "FieldTitle", + "id": "field ID", + }, + { + "__typename": "ProjectV2SingleSelectField", + "name": "Status", + "id": "status ID", + }, + { + "__typename": "ProjectV2IterationField", + "name": "Iterations", + "id": "iteration ID", + }, + }, + }, + }, + }, + }, + }) + + client := queries.NewTestClient() + + ios, _, stdout, _ := iostreams.Test() + ios.SetStdoutTTY(true) + config := listConfig{ + opts: listOpts{ + number: 1, + owner: "monalisa", + }, + client: client, + io: ios, + } + + err := runList(config) + assert.NoError(t, err) + assert.Equal(t, heredoc.Doc(` + NAME DATA TYPE ID + FieldTitle ProjectV2Field field ID + Status ProjectV2SingleSelectField status ID + Iterations ProjectV2IterationField iteration ID + `), stdout.String()) +} + func TestRunList_User(t *testing.T) { defer gock.Off() // gock.Observe(gock.DumpRequest) @@ -163,8 +258,6 @@ func TestRunList_User(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "monalisa", @@ -257,8 +350,6 @@ func TestRunList_Org(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "github", @@ -341,8 +432,6 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", @@ -409,8 +498,6 @@ func TestRunList_Empty(t *testing.T) { ios, _, _, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", diff --git a/pkg/cmd/project/item-list/item_list.go b/pkg/cmd/project/item-list/item_list.go index f2e9bf8a6..45ca0c77f 100644 --- a/pkg/cmd/project/item-list/item_list.go +++ b/pkg/cmd/project/item-list/item_list.go @@ -23,7 +23,6 @@ type listOpts struct { type listConfig struct { io *iostreams.IOStreams - tp *tableprinter.TablePrinter client *queries.Client opts listOpts } @@ -52,10 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C opts.number = int32(num) } - t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Type", "Title", "Number", "Repository", "ID")) config := listConfig{ io: f.IOStreams, - tp: t, client: client, opts: opts, } @@ -108,20 +105,22 @@ func printResults(config listConfig, items []queries.ProjectItem, login string) return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no items", config.opts.number, login)) } + tp := tableprinter.New(config.io, tableprinter.WithHeader("Type", "Title", "Number", "Repository", "ID")) + for _, i := range items { - config.tp.AddField(i.Type()) - config.tp.AddField(i.Title()) + tp.AddField(i.Type()) + tp.AddField(i.Title()) if i.Number() == 0 { - config.tp.AddField("") + tp.AddField("") } else { - config.tp.AddField(strconv.Itoa(i.Number())) + tp.AddField(strconv.Itoa(i.Number())) } - config.tp.AddField(i.Repo()) - config.tp.AddField(i.ID(), tableprinter.WithTruncate(nil)) - config.tp.EndRow() + tp.AddField(i.Repo()) + tp.AddField(i.ID(), tableprinter.WithTruncate(nil)) + tp.EndRow() } - return config.tp.Render() + return tp.Render() } func printJSON(config listConfig, project *queries.Project) error { diff --git a/pkg/cmd/project/item-list/item_list_test.go b/pkg/cmd/project/item-list/item_list_test.go index c36dfc589..f637ea99b 100644 --- a/pkg/cmd/project/item-list/item_list_test.go +++ b/pkg/cmd/project/item-list/item_list_test.go @@ -3,12 +3,12 @@ package itemlist import ( "testing" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/pkg/cmd/project/shared/queries" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/shlex" - "github.com/cli/cli/v2/internal/tableprinter" "github.com/stretchr/testify/assert" "gopkg.in/h2non/gock.v1" ) @@ -88,6 +88,115 @@ func TestNewCmdList(t *testing.T) { } } +func TestRunList_User_tty(t *testing.T) { + defer gock.Off() + // gock.Observe(gock.DumpRequest) + + // get user ID + gock.New("https://api.github.com"). + Post("/graphql"). + MatchType("json"). + JSON(map[string]interface{}{ + "query": "query UserOrgOwner.*", + "variables": map[string]interface{}{ + "login": "monalisa", + }, + }). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "id": "an ID", + }, + }, + "errors": []interface{}{ + map[string]interface{}{ + "type": "NOT_FOUND", + "path": []string{"organization"}, + }, + }, + }) + + // list project items + gock.New("https://api.github.com"). + Post("/graphql"). + JSON(map[string]interface{}{ + "query": "query UserProjectWithItems.*", + "variables": map[string]interface{}{ + "firstItems": queries.LimitDefault, + "afterItems": nil, + "firstFields": queries.LimitMax, + "afterFields": nil, + "login": "monalisa", + "number": 1, + }, + }). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "projectV2": map[string]interface{}{ + "items": map[string]interface{}{ + "nodes": []map[string]interface{}{ + { + "id": "issue ID", + "content": map[string]interface{}{ + "__typename": "Issue", + "title": "an issue", + "number": 1, + "repository": map[string]string{ + "nameWithOwner": "cli/go-gh", + }, + }, + }, + { + "id": "pull request ID", + "content": map[string]interface{}{ + "__typename": "PullRequest", + "title": "a pull request", + "number": 2, + "repository": map[string]string{ + "nameWithOwner": "cli/go-gh", + }, + }, + }, + { + "id": "draft issue ID", + "content": map[string]interface{}{ + "title": "draft issue", + "__typename": "DraftIssue", + }, + }, + }, + }, + }, + }, + }, + }) + + client := queries.NewTestClient() + + ios, _, stdout, _ := iostreams.Test() + ios.SetStdoutTTY(true) + config := listConfig{ + opts: listOpts{ + number: 1, + owner: "monalisa", + }, + client: client, + io: ios, + } + + err := runList(config) + assert.NoError(t, err) + assert.Equal(t, heredoc.Doc(` + TYPE TITLE NUMBER REPOSITORY ID + Issue an issue 1 cli/go-gh issue ID + PullRequest a pull request 2 cli/go-gh pull request ID + DraftIssue draft issue draft issue ID + `), stdout.String()) +} + func TestRunList_User(t *testing.T) { defer gock.Off() // gock.Observe(gock.DumpRequest) @@ -178,8 +287,6 @@ func TestRunList_User(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "monalisa", @@ -286,8 +393,6 @@ func TestRunList_Org(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "github", @@ -384,8 +489,6 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", diff --git a/pkg/cmd/project/list/list.go b/pkg/cmd/project/list/list.go index d3168d7eb..88f419b90 100644 --- a/pkg/cmd/project/list/list.go +++ b/pkg/cmd/project/list/list.go @@ -23,7 +23,6 @@ type listOpts struct { } type listConfig struct { - tp *tableprinter.TablePrinter client *queries.Client opts listOpts URLOpener func(string) error @@ -52,9 +51,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C URLOpener := func(url string) error { return f.Browser.Browse(url) } - t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Number", "Title", "State", "ID")) + config := listConfig{ - tp: t, client: client, opts: opts, URLOpener: URLOpener, @@ -68,7 +66,6 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C return runList(config) }, } - listCmd.Flags().StringVar(&opts.owner, "owner", "", "Login of the owner") listCmd.Flags().BoolVarP(&opts.closed, "closed", "", false, "Include closed projects") listCmd.Flags().BoolVarP(&opts.web, "web", "w", false, "Open projects list in the browser") @@ -157,21 +154,23 @@ func printResults(config listConfig, projects []queries.Project, owner string) e return cmdutil.NewNoResultsError(fmt.Sprintf("No projects found for %s", owner)) } + tp := tableprinter.New(config.io, tableprinter.WithHeader("Number", "Title", "State", "ID")) + for _, p := range projects { - config.tp.AddField(strconv.Itoa(int(p.Number)), tableprinter.WithTruncate(nil)) - config.tp.AddField(p.Title) + tp.AddField(strconv.Itoa(int(p.Number)), tableprinter.WithTruncate(nil)) + tp.AddField(p.Title) var state string if p.Closed { state = "closed" } else { state = "open" } - config.tp.AddField(state) - config.tp.AddField(p.ID, tableprinter.WithTruncate(nil)) - config.tp.EndRow() + tp.AddField(state) + tp.AddField(p.ID, tableprinter.WithTruncate(nil)) + tp.EndRow() } - return config.tp.Render() + return tp.Render() } func printJSON(config listConfig, projects []queries.Project, totalCount int) error { diff --git a/pkg/cmd/project/list/list_test.go b/pkg/cmd/project/list/list_test.go index bafdf8e93..bb840caa6 100644 --- a/pkg/cmd/project/list/list_test.go +++ b/pkg/cmd/project/list/list_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/cli/cli/v2/internal/tableprinter" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/pkg/cmd/project/shared/queries" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -91,6 +91,85 @@ func TestNewCmdlist(t *testing.T) { } } +func TestRunListTTY(t *testing.T) { + defer gock.Off() + gock.Observe(gock.DumpRequest) + // get user ID + gock.New("https://api.github.com"). + Post("/graphql"). + MatchType("json"). + JSON(map[string]interface{}{ + "query": "query UserOrgOwner.*", + "variables": map[string]interface{}{ + "login": "monalisa", + }, + }). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "id": "an ID", + }, + }, + "errors": []interface{}{ + map[string]interface{}{ + "type": "NOT_FOUND", + "path": []string{"organization"}, + }, + }, + }) + + gock.New("https://api.github.com"). + Post("/graphql"). + Reply(200). + JSON(map[string]interface{}{ + "data": map[string]interface{}{ + "user": map[string]interface{}{ + "login": "monalisa", + "projectsV2": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "title": "Project 1", + "shortDescription": "Short description 1", + "url": "url1", + "closed": false, + "ID": "id-1", + "number": 1, + }, + map[string]interface{}{ + "title": "Project 2", + "shortDescription": "", + "url": "url2", + "closed": true, + "ID": "id-2", + "number": 2, + }, + }, + }, + }, + }, + }) + + client := queries.NewTestClient() + + ios, _, stdout, _ := iostreams.Test() + ios.SetStdoutTTY(true) + config := listConfig{ + opts: listOpts{ + owner: "monalisa", + }, + client: client, + io: ios, + } + + err := runList(config) + assert.NoError(t, err) + assert.Equal(t, heredoc.Doc(` + NUMBER TITLE STATE ID + 1 Project 1 open id-1 + `), stdout.String()) +} + func TestRunList(t *testing.T) { defer gock.Off() gock.Observe(gock.DumpRequest) @@ -154,8 +233,6 @@ func TestRunList(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "monalisa", }, @@ -226,8 +303,6 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "@me", }, @@ -298,8 +373,6 @@ func TestRunListViewer(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{}, client: client, io: ios, @@ -377,8 +450,6 @@ func TestRunListOrg(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "github", }, @@ -432,8 +503,6 @@ func TestRunListEmpty(t *testing.T) { ios, _, _, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{}, client: client, io: ios, @@ -509,8 +578,6 @@ func TestRunListWithClosed(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. - tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "monalisa", closed: true,