diff --git a/commands/issue/issueutils/utils.go b/commands/issue/issueutils/utils.go index 47222fdd..4bf770d4 100644 --- a/commands/issue/issueutils/utils.go +++ b/commands/issue/issueutils/utils.go @@ -5,50 +5,43 @@ import ( "strings" "github.com/profclems/glab/internal/utils" + "github.com/profclems/glab/pkg/tableprinter" - "github.com/gosuri/uitable" "github.com/xanzy/go-gitlab" ) -func DisplayAllIssues(m []*gitlab.Issue, projectID string) *uitable.Table { - return utils.DisplayList(utils.ListInfo{ - Name: "issues", - Columns: []string{"IssueID", "Title", "Labels", "CreatedAt"}, - Total: len(m), - GetCellValue: func(ri int, ci int) interface{} { - issue := m[ri] - switch ci { - case 0: - if issue.State == "opened" { - return utils.Green(fmt.Sprintf("#%d", issue.IID)) - } else { - return utils.Red(fmt.Sprintf("#%d", issue.IID)) - } - case 1: - return issue.Title - case 2: - if len(issue.Labels) > 0 { - return fmt.Sprintf("(%s)", utils.Cyan(strings.Trim(strings.Join(issue.Labels, ", "), ","))) - } - return "" - case 3: - return utils.Gray(utils.TimeToPrettyTimeAgo(*issue.CreatedAt)) - default: - return "" - } - }, - }, projectID) +func DisplayIssueList(issues []*gitlab.Issue, projectID string) string { + table := tableprinter.NewTablePrinter() + for _, issue := range issues { + table.AddCell(IssueState(issue)) + table.AddCell(issue.Title) + + if len(issue.Labels) > 0 { + table.AddCellf("(%s)", utils.Cyan(strings.Trim(strings.Join(issue.Labels, ", "), ","))) + } else { + table.AddCell("") + } + + table.AddCell(utils.Gray(utils.TimeToPrettyTimeAgo(*issue.CreatedAt))) + table.EndRow() + } + + return table.Render() } func DisplayIssue(i *gitlab.Issue) string { duration := utils.TimeToPrettyTimeAgo(*i.CreatedAt) - var issueID string + issueID := IssueState(i) + + return fmt.Sprintf("%s %s (%s)\n %s\n", + issueID, i.Title, duration, i.WebURL) +} + +func IssueState(i *gitlab.Issue) (issueID string) { if i.State == "opened" { issueID = utils.Green(fmt.Sprintf("#%d", i.IID)) } else { issueID = utils.Red(fmt.Sprintf("#%d", i.IID)) } - - return fmt.Sprintf("%s %s (%s)\n %s\n", - issueID, i.Title, duration, i.WebURL) + return } diff --git a/commands/issue/list/issue_list.go b/commands/issue/list/issue_list.go index 31596f73..b5c4f9a3 100644 --- a/commands/issue/list/issue_list.go +++ b/commands/issue/list/issue_list.go @@ -21,8 +21,10 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { var ( - state string - err error + state string + err error + listType string + titleQualifier string ) apiClient, err := f.HttpClient() @@ -39,13 +41,18 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { state = "all" } else if lb, _ := cmd.Flags().GetBool("closed"); lb { state = "closed" + titleQualifier = "closed" } else { state = "opened" + titleQualifier = "open" } opts := &gitlab.ListProjectIssuesOptions{ State: gitlab.String(state), } + opts.Page = 1 + opts.PerPage = 30 + if lb, _ := cmd.Flags().GetString("assignee"); lb != "" { opts.AssigneeUsername = gitlab.String(lb) } @@ -54,29 +61,43 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { lb, } opts.Labels = label + listType = "search" } if lb, _ := cmd.Flags().GetString("milestone"); lb != "" { opts.Milestone = gitlab.String(lb) + listType = "search" } if lb, _ := cmd.Flags().GetBool("confidential"); lb { opts.Confidential = gitlab.Bool(lb) + listType = "search" } if p, _ := cmd.Flags().GetInt("page"); p != 0 { opts.Page = p + listType = "search" } if p, _ := cmd.Flags().GetInt("per-page"); p != 0 { opts.PerPage = p + listType = "search" } if lb, _ := cmd.Flags().GetBool("mine"); lb { u, _ := api.CurrentUser(nil) opts.AssigneeUsername = gitlab.String(u.Username) + listType = "search" } issues, err := api.ListIssues(apiClient, repo.FullName(), opts) if err != nil { return err } - fmt.Fprintln(utils.ColorableOut(cmd), issueutils.DisplayAllIssues(issues, repo.FullName())) + + title := utils.NewListTitle(titleQualifier + " issue") + title.RepoName = repo.FullName() + title.Page = opts.Page + title.ListActionType = listType + title.CurrentPageTotal = len(issues) + + fmt.Fprintf(utils.ColorableOut(cmd), "%s\n%s\n", title.Describe(), issueutils.DisplayIssueList(issues, repo.FullName())) + return nil }, @@ -90,7 +111,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { issueListCmd.Flags().BoolP("opened", "o", false, "Get only opened issues") issueListCmd.Flags().BoolP("confidential", "", false, "Filter by confidential issues") issueListCmd.Flags().IntP("page", "p", 1, "Page number") - issueListCmd.Flags().IntP("per-page", "P", 20, "Number of items to list per page") + issueListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page. (default 30)") return issueListCmd } diff --git a/commands/label/list/label_list.go b/commands/label/list/label_list.go index ff0ec92c..9d9f70bc 100644 --- a/commands/label/list/label_list.go +++ b/commands/label/list/label_list.go @@ -2,7 +2,6 @@ package list import ( "fmt" - "strings" "github.com/profclems/glab/pkg/api" @@ -34,9 +33,10 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { return err } - cfg, _ := f.Config() - l := &gitlab.ListLabelsOptions{} + + l.WithCounts = gitlab.Bool(true) + if p, _ := cmd.Flags().GetInt("page"); p != 0 { l.Page = p } @@ -61,14 +61,14 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { fmt.Fprintln(out, utils.Indent(labelPrintInfo, " ")) // Cache labels for host - labelNames := make([]string, 0, len(labels)) - for _, label := range labels { - labelNames = append(labelNames, label.Name) - } - labelsEntry := strings.Join(labelNames, ",") - if err := cfg.Set(repo.RepoHost(), "project_labels", labelsEntry); err != nil { - _ = cfg.Write() - } + //labelNames := make([]string, 0, len(labels)) + //for _, label := range labels { + // labelNames = append(labelNames, label.Name) + //} + //labelsEntry := strings.Join(labelNames, ",") + //if err := cfg.Set(repo.RepoHost(), "project_labels", labelsEntry); err != nil { + // _ = cfg.Write() + //} return nil @@ -76,7 +76,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { } labelListCmd.Flags().IntP("page", "p", 1, "Page number") - labelListCmd.Flags().IntP("per-page", "P", 20, "Number of items to list per page") + labelListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page") return labelListCmd } diff --git a/commands/mr/issues/mr_issues.go b/commands/mr/issues/mr_issues.go index 2983f4f9..ed4ce720 100644 --- a/commands/mr/issues/mr_issues.go +++ b/commands/mr/issues/mr_issues.go @@ -42,7 +42,14 @@ func NewCmdIssues(f *cmdutils.Factory) *cobra.Command { if err != nil { return err } - fmt.Fprintln(out, issueutils.DisplayAllIssues(mrIssues, repo.FullName())) + + title := utils.NewListTitle("issue") + title.RepoName = repo.FullName() + title.Page = 0 + title.ListActionType = "search" + title.CurrentPageTotal = len(mrIssues) + + fmt.Fprintf(out, "%s\n%s\n", title.Describe(), issueutils.DisplayIssueList(mrIssues, repo.FullName())) return nil }, } diff --git a/commands/mr/list/mr_list.go b/commands/mr/list/mr_list.go index ecf75dcf..39791de3 100644 --- a/commands/mr/list/mr_list.go +++ b/commands/mr/list/mr_list.go @@ -22,6 +22,9 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { var state string var err error + var listType string + var titleQualifier string + out := utils.ColorableOut(cmd) apiClient, err := f.HttpClient() @@ -38,23 +41,31 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { state = "all" } else if lb, _ := cmd.Flags().GetBool("closed"); lb { state = "closed" + titleQualifier = state } else if lb, _ := cmd.Flags().GetBool("merged"); lb { state = "merged" + titleQualifier = state } else { state = "opened" + titleQualifier = "open" } l := &gitlab.ListProjectMergeRequestsOptions{ State: gitlab.String(state), } + l.Page = 1 + l.PerPage = 30 + if lb, _ := cmd.Flags().GetString("label"); lb != "" { label := gitlab.Labels{ lb, } l.Labels = label + listType = "search" } if lb, _ := cmd.Flags().GetString("milestone"); lb != "" { l.Milestone = gitlab.String(lb) + listType = "search" } if p, _ := cmd.Flags().GetInt("page"); p != 0 { l.Page = p @@ -65,6 +76,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { if mine, _ := cmd.Flags().GetBool("mine"); mine { l.Scope = gitlab.String("assigned_to_me") + listType = "search" } assigneeIds := make([]int, 0) @@ -92,7 +104,13 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { return err } - fmt.Fprintln(out, mrutils.DisplayAllMRs(mergeRequests, repo.FullName())) + title := utils.NewListTitle(titleQualifier + " merge request") + title.RepoName = repo.FullName() + title.Page = l.Page + title.ListActionType = listType + title.CurrentPageTotal = len(mergeRequests) + + fmt.Fprintf(out, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(mergeRequests, repo.FullName())) return nil }, } @@ -101,10 +119,10 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { mrListCmd.Flags().StringP("milestone", "", "", "Filter merge request by milestone ") mrListCmd.Flags().BoolP("all", "a", false, "Get all merge requests") mrListCmd.Flags().BoolP("closed", "c", false, "Get only closed merge requests") - mrListCmd.Flags().BoolP("opened", "o", false, "Get only opened merge requests") + mrListCmd.Flags().BoolP("opened", "o", false, "Get only open merge requests") mrListCmd.Flags().BoolP("merged", "m", false, "Get only merged merge requests") mrListCmd.Flags().IntP("page", "p", 1, "Page number") - mrListCmd.Flags().IntP("per-page", "P", 20, "Number of items to list per page") + mrListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page. (default 30)") mrListCmd.Flags().BoolP("mine", "", false, "Get only merge requests assigned to me") mrListCmd.Flags().StringSliceP("assignee", "", []string{}, "Get only merge requests assigned to users") diff --git a/commands/mr/mrutils/mrutils.go b/commands/mr/mrutils/mrutils.go index e4eb6865..6c6f715b 100644 --- a/commands/mr/mrutils/mrutils.go +++ b/commands/mr/mrutils/mrutils.go @@ -3,45 +3,35 @@ package mrutils import ( "fmt" - "github.com/gosuri/uitable" "github.com/profclems/glab/internal/utils" + "github.com/profclems/glab/pkg/tableprinter" "github.com/xanzy/go-gitlab" ) func DisplayMR(mr *gitlab.MergeRequest) string { - var mrID string + mrID := MRState(mr) + return fmt.Sprintf("%s %s (%s)\n %s\n", + mrID, mr.Title, mr.SourceBranch, mr.WebURL) +} - if mr.State == "opened" { - mrID = utils.Green(fmt.Sprintf("!%d", mr.IID)) +func MRState(m *gitlab.MergeRequest) string { + if m.State == "opened" { + return utils.Green(fmt.Sprintf("!%d", m.IID)) + } else if m.State == "merged" { + return utils.Blue(fmt.Sprintf("!%d", m.IID)) } else { - mrID = utils.Red(fmt.Sprintf("!%d", mr.IID)) + return utils.Red(fmt.Sprintf("!%d", m.IID)) } - - return fmt.Sprintf("%s %s (%s)\n %s\n", - mrID, mr.Title, mr.SourceBranch, mr.WebURL) } -func DisplayAllMRs(m []*gitlab.MergeRequest, projectID string) *uitable.Table { - return utils.DisplayList(utils.ListInfo{ - Name: "Merge Requests", - Columns: []string{"ID", "Title", "Branch"}, - Total: len(m), - GetCellValue: func(ri int, ci int) interface{} { - mr := m[ri] - switch ci { - case 0: - if mr.State == "opened" { - return utils.Green(fmt.Sprintf("!%d", mr.IID)) - } else { - return utils.Red(fmt.Sprintf("!%d", mr.IID)) - } - case 1: - return mr.Title - case 2: - return utils.Cyan(fmt.Sprintf("(%s) ← (%s)", mr.TargetBranch, mr.SourceBranch)) - default: - return "" - } - }, - }, projectID) +func DisplayAllMRs(mrs []*gitlab.MergeRequest, projectID string) string { + table := tableprinter.NewTablePrinter() + for _, m := range mrs { + table.AddCell(MRState(m)) + table.AddCell(m.Title) + table.AddCell(utils.Cyan(fmt.Sprintf("(%s) ← (%s)", m.TargetBranch, m.SourceBranch))) + table.EndRow() + } + + return table.Render() } diff --git a/commands/pipeline/list/pipeline_list.go b/commands/pipeline/list/pipeline_list.go index 215e224d..2e936b44 100644 --- a/commands/pipeline/list/pipeline_list.go +++ b/commands/pipeline/list/pipeline_list.go @@ -25,6 +25,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { var err error + var titleQualifier string out := utils.ColorableOut(cmd) @@ -39,8 +40,12 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { } l := &gitlab.ListProjectPipelinesOptions{} + l.Page = 1 + l.PerPage = 30 + if m, _ := cmd.Flags().GetString("status"); m != "" { l.Status = gitlab.BuildState(gitlab.BuildStateValue(m)) + titleQualifier = m } if m, _ := cmd.Flags().GetString("orderBy"); m != "" { l.OrderBy = gitlab.String(m) @@ -60,7 +65,12 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { return err } - fmt.Fprintln(out, pipelineutils.DisplayMultiplePipelines(pipes, repo.FullName())) + title := utils.NewListTitle(fmt.Sprintf("%s pipeline", titleQualifier)) + title.RepoName = repo.FullName() + title.Page = l.Page + title.CurrentPageTotal = len(pipes) + + fmt.Fprintf(out, "%s\n%s\n", title.Describe(), pipelineutils.DisplayMultiplePipelines(pipes, repo.FullName())) return nil }, } @@ -68,7 +78,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command { pipelineListCmd.Flags().StringP("orderBy", "o", "", "Order pipeline by ") pipelineListCmd.Flags().StringP("sort", "", "desc", "Sort pipeline by {asc|desc}. (Defaults to desc)") pipelineListCmd.Flags().IntP("page", "p", 1, "Page number") - pipelineListCmd.Flags().IntP("per-page", "P", 20, "Number of items to list per page") + pipelineListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page. (default 30)") return pipelineListCmd } diff --git a/commands/pipeline/pipelineutils/utils.go b/commands/pipeline/pipelineutils/utils.go index 6bb6f6cf..58060f62 100644 --- a/commands/pipeline/pipelineutils/utils.go +++ b/commands/pipeline/pipelineutils/utils.go @@ -5,14 +5,13 @@ import ( "fmt" "io" "io/ioutil" - "os" "sync" "time" "github.com/profclems/glab/internal/utils" "github.com/profclems/glab/pkg/api" + "github.com/profclems/glab/pkg/tableprinter" - "github.com/juju/ansiterm/tabwriter" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" ) @@ -23,15 +22,10 @@ var ( ) func DisplayMultiplePipelines(p []*gitlab.PipelineInfo, projectID string) string { - // initialize tabwriter - w := new(tabwriter.Writer) - // minwidth, tabwidth, padding, padchar, flags - w.Init(os.Stdout, 8, 8, 0, '\t', 0) + table := tableprinter.NewTablePrinter() - defer w.Flush() if len(p) > 0 { - pipelinePrint := fmt.Sprintf("Showing pipelines %d of %d on %s\n\n", len(p), len(p), projectID) for _, pipeline := range p { duration := utils.TimeToPrettyTimeAgo(*pipeline.CreatedAt) @@ -44,10 +38,10 @@ func DisplayMultiplePipelines(p []*gitlab.PipelineInfo, projectID string) string pipeState = utils.Gray(fmt.Sprintf("(%s) • #%d", pipeline.Status, pipeline.ID)) } - pipelinePrint += fmt.Sprintf("%s\t%s\t%s\n", pipeState, pipeline.Ref, utils.Magenta("("+duration+")")) + table.AddRow(pipeState, pipeline.Ref, utils.Magenta("("+duration+")")) } - return pipelinePrint + return table.Render() } return "No Pipelines available on " + projectID diff --git a/commands/pipeline/status/pipeline_status.go b/commands/pipeline/status/pipeline_status.go index 3fab8ebd..c1653e5c 100644 --- a/commands/pipeline/status/pipeline_status.go +++ b/commands/pipeline/status/pipeline_status.go @@ -65,7 +65,8 @@ func NewCmdStatus(f *cmdutils.Factory) *cobra.Command { if err != nil { return err } - if len(pipes) == 1 { + + if len(pipes) > 0 { runningPipeline := pipes[0] isRunning := true retry := "Exit" @@ -90,12 +91,13 @@ func NewCmdStatus(f *cmdutils.Factory) *cobra.Command { status = utils.Gray(s) } //fmt.Println(job.Tag) - _, _ = fmt.Fprintf(writer, "(%s) • %s\t\t%s\t\t%s\n", status, duration, job.Stage, job.Name) + fmt.Fprintf(writer, "(%s) • %s\t%s\t\t%s\n", status, utils.Gray(duration), job.Stage, job.Name) } - _, _ = fmt.Fprintf(writer.Newline(), "\n%s\n", runningPipeline.WebURL) - _, _ = fmt.Fprintf(writer.Newline(), "SHA: %s\n", runningPipeline.SHA) - _, _ = fmt.Fprintf(writer.Newline(), "Pipeline State: %s\n", runningPipeline.Status) + fmt.Fprintf(writer.Newline(), "\n%s\n", runningPipeline.WebURL) + fmt.Fprintf(writer.Newline(), "SHA: %s\n", runningPipeline.SHA) + fmt.Fprintf(writer.Newline(), "Pipeline State: %s\n", runningPipeline.Status) + if runningPipeline.Status == "running" && live { pipes, err = api.GetPipelines(apiClient, l, repo.FullName()) if err != nil { @@ -103,14 +105,12 @@ func NewCmdStatus(f *cmdutils.Factory) *cobra.Command { } runningPipeline = pipes[0] } else { - if runningPipeline.Status == "failed" || runningPipeline.Status == "canceled" { - prompt := &survey.Select{ - Message: "Choose an action:", - Options: []string{"View Logs", "Retry", "Exit"}, - Default: "Exit", - } - _ = survey.AskOne(prompt, &retry) + prompt := &survey.Select{ + Message: "Choose an action:", + Options: []string{"View Logs", "Retry", "Exit"}, + Default: "Exit", } + _ = survey.AskOne(prompt, &retry) if retry != "" && retry != "Exit" { if retry == "View Logs" { isRunning = false @@ -130,13 +130,15 @@ func NewCmdStatus(f *cmdutils.Factory) *cobra.Command { isRunning = false } } - time.Sleep(time.Millisecond * 0) + writer.Stop() + if retry == "View Logs" { // ToDo: bad idea to call another sub-command. should be fixed to avoid cyclo imports // and the a shared function placed in the pipeutils sub-module return ciTraceCmd.TraceCmdFunc(cmd, args, f) } } + return nil } redCheck := utils.Red("✘") fmt.Fprintf(out, "%s No pipelines running or available on %s branch\n", redCheck, branch) diff --git a/commands/project/search/project_search.go b/commands/project/search/project_search.go index 34761321..a881e940 100644 --- a/commands/project/search/project_search.go +++ b/commands/project/search/project_search.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + "github.com/profclems/glab/pkg/tableprinter" + "github.com/profclems/glab/commands/cmdutils" "github.com/profclems/glab/internal/utils" @@ -39,35 +41,27 @@ func NewCmdSearch(f *cmdutils.Factory) *cobra.Command { return err } - fmt.Fprintln(out, utils.DisplayList(utils.ListInfo{ - Name: "Projects", - Columns: []string{"", "", "", ""}, - Total: len(projects), - Description: fmt.Sprintf("Showing results for \"%s\"", search), - EmptyMessage: fmt.Sprintf("No results found for \"%s\"", search), - TableWrap: true, - GetCellValue: func(ri int, ci int) interface{} { - p := projects[ri] - switch ci { - case 0: - return utils.Green(string(rune(p.ID))) - case 1: - var description string - if p.Description != "" { - description = fmt.Sprintf("\n%s", utils.Cyan(p.Description)) - } - return fmt.Sprintf("%s%s\n%s", - strings.ReplaceAll(p.PathWithNamespace, "/", " / "), - description, utils.Gray(p.WebURL)) - case 2: - return fmt.Sprintf("%d stars %d forks %d issues", p.StarCount, p.ForksCount, p.OpenIssuesCount) - case 3: - return "updated " + utils.TimeToPrettyTimeAgo(*p.LastActivityAt) - default: - return "" - } - }, - }, "")) + title := fmt.Sprintf("Showing results for \"%s\"", search) + if len(projects) == 0 { + title = fmt.Sprintf("No results found for \"%s\"", search) + } + + table := tableprinter.NewTablePrinter() + for _, p := range projects { + table.AddCell(utils.Green(string(rune(p.ID)))) + + var description string + if p.Description != "" { + description = fmt.Sprintf("\n%s", utils.Cyan(p.Description)) + } + + table.AddCellf("%s%s\n%s", strings.ReplaceAll(p.PathWithNamespace, "/", " / "), description, utils.Gray(p.WebURL)) + table.AddCellf("%d stars %d forks %d issues", p.StarCount, p.ForksCount, p.OpenIssuesCount) + table.AddCellf("updated %s", utils.TimeToPrettyTimeAgo(*p.LastActivityAt)) + table.EndRow() + } + + fmt.Fprintf(out, "%s\n%s\n", title, table.Render()) return nil }, } diff --git a/commands/release/list/release_list.go b/commands/release/list/release_list.go index 231841e0..fa2b5ffa 100644 --- a/commands/release/list/release_list.go +++ b/commands/release/list/release_list.go @@ -62,11 +62,19 @@ func listReleases(cmd *cobra.Command, args []string) error { glamourStyle, _ := cfg.Get(repo.RepoHost(), "glamour_style") fmt.Fprintln(utils.ColorableOut(cmd), releaseutils.DisplayRelease(release, glamourStyle)) } else { + l.PerPage = 30 + releases, err := api.ListReleases(apiClient, repo.FullName(), l) if err != nil { return err } - fmt.Fprintln(utils.ColorableOut(cmd), releaseutils.DisplayAllReleases(releases, repo.FullName())) + + title := utils.NewListTitle("release") + title.RepoName = repo.FullName() + title.Page = 0 + title.CurrentPageTotal = len(releases) + + fmt.Fprintf(utils.ColorableOut(cmd), "%s\n%s\n", title.Describe(), releaseutils.DisplayAllReleases(releases, repo.FullName())) } return nil } diff --git a/commands/release/list/release_list_test.go b/commands/release/list/release_list_test.go index 7f4aedb8..fa7462db 100644 --- a/commands/release/list/release_list_test.go +++ b/commands/release/list/release_list_test.go @@ -75,7 +75,7 @@ func TestNewCmdReleaseList(t *testing.T) { name: "releases list on test repo", wantErr: false, stdOutFunc: func(t *testing.T, out string) { - assert.Contains(t, out, "Showing releases 1 of 1 on glab-cli/test") + assert.Contains(t, out, "Showing 1 release on glab-cli/test") }, }, { @@ -91,7 +91,7 @@ func TestNewCmdReleaseList(t *testing.T) { wantErr: false, args: "-R profclems/glab", stdOutFunc: func(t *testing.T, out string) { - assert.Contains(t, out, "Showing releases 1 of 1 on profclems/glab") + assert.Contains(t, out, "Showing 1 release on profclems/glab") }, }, { diff --git a/commands/release/releaseutils/releaseutils.go b/commands/release/releaseutils/releaseutils.go index c260e928..0cd4e39e 100644 --- a/commands/release/releaseutils/releaseutils.go +++ b/commands/release/releaseutils/releaseutils.go @@ -3,30 +3,19 @@ package releaseutils import ( "fmt" - "github.com/gosuri/uitable" "github.com/profclems/glab/internal/utils" + "github.com/profclems/glab/pkg/tableprinter" + "github.com/xanzy/go-gitlab" ) -func DisplayAllReleases(rs []*gitlab.Release, repo string) *uitable.Table { - return utils.DisplayList(utils.ListInfo{ - Name: "releases", - Columns: []string{"Name", "Tag", "CreatedAt"}, - Total: len(rs), - GetCellValue: func(ri int, ci int) interface{} { - row := rs[ri] - switch ci { - case 0: - return row.Name - case 1: - return row.TagName - case 2: - return utils.Gray(utils.TimeToPrettyTimeAgo(*row.CreatedAt)) - default: - return "" - } - }, - }, repo) +func DisplayAllReleases(releases []*gitlab.Release, repoName string) string { + table := tableprinter.NewTablePrinter() + for _, r := range releases { + table.AddRow(r.Name, r.TagName, utils.Gray(utils.TimeToPrettyTimeAgo(*r.CreatedAt))) + } + + return table.Render() } func RenderReleaseAssertLinks(assets []*gitlab.ReleaseLink) string { diff --git a/internal/utils/display.go b/internal/utils/display.go index 4a5a3ebd..dca09901 100644 --- a/internal/utils/display.go +++ b/internal/utils/display.go @@ -4,73 +4,80 @@ import ( "fmt" "regexp" "strings" - - "github.com/gosuri/uitable" ) -// ListInfo represents the parameters required to display a list result. -type ListInfo struct { +var lineRE = regexp.MustCompile(`(?m)^`) + +func Indent(s, indent string) string { + if strings.TrimSpace(s) == "" { + return s + } + return lineRE.ReplaceAllLiteralString(s, indent) +} + +type ListTitleOptions struct { // Name of the List to be used in constructing Description and EmptyMessage if not provided. Name string - // List of columns to display - Columns []string - // Total number of record. Ideally size of the List. + // Page represents the page number of the current page + Page int + // CurrentPageTotal is the total number of items in current page + CurrentPageTotal int + // Total number of records. Default is the total number of rows. + // Can be set to be greater than the total number of rows especially, if the list is paginated Total int - // Function to pick a cell value from cell index - GetCellValue func(int, int) interface{} - // Optional. Description of the List. If not provided, default one constructed from list Name. - Description string + // RepoName represents the name of the project or repository + RepoName string + // ListActionType should be either "search" or "list". Default is list + ListActionType string // Optional. EmptyMessage to display when List is empty. If not provided, default one constructed from list Name. EmptyMessage string - // TableWrap wraps the contents when the column length exceeds the maximum width - TableWrap bool - // PrintHeader prints the headers using the Columns provided - PrintHeader bool } -// Prints the list data on console -func DisplayList(lInfo ListInfo, projectID string) *uitable.Table { - table := uitable.New() - table.MaxColWidth = 70 - table.Wrap = lInfo.TableWrap +func NewListTitle(listName string) ListTitleOptions { + return ListTitleOptions{ + Name: strings.TrimSpace(listName), + ListActionType: "list", + Page: 1, + } +} - if lInfo.Total > 0 { - description := lInfo.Description - if description == "" { - description = fmt.Sprintf("Showing %s %d of %d on %s\n", lInfo.Name, lInfo.Total, lInfo.Total, projectID) - } - table.AddRow(description) - if lInfo.PrintHeader { - header := make([]interface{}, len(lInfo.Columns)) - for ci, c := range lInfo.Columns { - header[ci] = c - } - table.AddRow(header...) - } +func (opts *ListTitleOptions) Describe() string { + var pageNumInfo string + var pageInfo string - for ri := 0; ri < lInfo.Total; ri++ { - row := make([]interface{}, len(lInfo.Columns)) - for ci := range lInfo.Columns { - row[ci] = lInfo.GetCellValue(ri, ci) - } - table.AddRow(row...) - } + if opts.Total != 0 { + opts.Name = pluralizeName(opts.Total, opts.Name) + pageNumInfo = fmt.Sprintf("%d of %d", opts.CurrentPageTotal, opts.Total) } else { - emptyMessage := lInfo.EmptyMessage - if emptyMessage == "" { - emptyMessage = fmt.Sprintf("No %s available on %s", lInfo.Name, projectID) - } - table.AddRow(emptyMessage) + opts.Name = pluralizeName(opts.CurrentPageTotal, opts.Name) + pageNumInfo = fmt.Sprintf("%d", opts.CurrentPageTotal) } - return table -} + if opts.Page != 0 { + pageInfo = fmt.Sprintf("(Page %d)", opts.Page) + } -var lineRE = regexp.MustCompile(`(?m)^`) + if opts.ListActionType == "search" { + if opts.CurrentPageTotal > 0 { + return fmt.Sprintf("Showing %s %s in %s that match your search %s\n", pageNumInfo, opts.Name, + opts.RepoName, pageInfo) + } -func Indent(s, indent string) string { - if strings.TrimSpace(s) == "" { - return s + return fmt.Sprintf("No %s match your search in %s", opts.Name, opts.RepoName) } - return lineRE.ReplaceAllLiteralString(s, indent) + + if opts.CurrentPageTotal > 0 { + return fmt.Sprintf("Showing %s %s on %s %s\n", pageNumInfo, opts.Name, opts.RepoName, pageInfo) + } + + emptyMessage := opts.EmptyMessage + if emptyMessage == "" { + return fmt.Sprintf("No %s available on %s", opts.Name, opts.RepoName) + } + + return emptyMessage +} + +func pluralizeName(num int, thing string) string { + return strings.TrimPrefix(Pluralize(num, thing), fmt.Sprintf("%d ", num)) } diff --git a/pkg/api/default.go b/pkg/api/default.go new file mode 100644 index 00000000..ac3519ab --- /dev/null +++ b/pkg/api/default.go @@ -0,0 +1,3 @@ +package api + +var DefaultListLimit = 30 diff --git a/pkg/api/issue.go b/pkg/api/issue.go index 34e03450..36b91642 100644 --- a/pkg/api/issue.go +++ b/pkg/api/issue.go @@ -9,6 +9,9 @@ var ListIssueNotes = func(client *gitlab.Client, projectID interface{}, issueID if client == nil { client = apiClient } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } notes, _, err := client.Notes.ListIssueNotes(projectID, issueID, opts) if err != nil { return nil, err @@ -41,6 +44,12 @@ var GetIssue = func(client *gitlab.Client, projectID interface{}, issueID int) ( } var ListIssues = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectIssuesOptions) ([]*gitlab.Issue, error) { + if client == nil { + client = apiClient + } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } issues, _, err := client.Issues.ListProjectIssues(projectID, opts) if err != nil { return nil, err diff --git a/pkg/api/label.go b/pkg/api/label.go index 6a61a6fd..ab0c4b12 100644 --- a/pkg/api/label.go +++ b/pkg/api/label.go @@ -17,6 +17,11 @@ var ListLabels = func(client *gitlab.Client, projectID interface{}, opts *gitlab if client == nil { client = apiClient } + + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + label, _, err := client.Labels.ListLabels(projectID, opts) if err != nil { return nil, err diff --git a/pkg/api/merge_request.go b/pkg/api/merge_request.go index 66d8cfba..cebcc43a 100644 --- a/pkg/api/merge_request.go +++ b/pkg/api/merge_request.go @@ -44,6 +44,10 @@ var ListMRs = func(client *gitlab.Client, projectID interface{}, opts *gitlab.Li if client == nil { client = apiClient } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + mrs, _, err := client.MergeRequests.ListProjectMergeRequests(projectID, opts) if err != nil { return nil, err @@ -56,6 +60,10 @@ var ListMRsWithAssignees = func(client *gitlab.Client, projectID interface{}, op if client == nil { client = apiClient } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + mrs := make([]*gitlab.MergeRequest, 0) for _, id := range assigneeIds { opts.AssigneeID = gitlab.Int(id) @@ -146,6 +154,10 @@ var ListMRNotes = func(client *gitlab.Client, projectID interface{}, mrID int, o client = apiClient } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + notes, _, err := client.Notes.ListMergeRequestNotes(projectID, mrID, opts) if err != nil { return notes, err diff --git a/pkg/api/pipeline.go b/pkg/api/pipeline.go index 9dc2688c..67e57dd0 100644 --- a/pkg/api/pipeline.go +++ b/pkg/api/pipeline.go @@ -110,10 +110,14 @@ var GetJobs = func(client *gitlab.Client, repo string, opts *gitlab.ListJobsOpti return jobs, nil } -var GetPipelines = func(client *gitlab.Client, l *gitlab.ListProjectPipelinesOptions, repo string) ([]*gitlab.PipelineInfo, error) { +var GetPipelines = func(client *gitlab.Client, l *gitlab.ListProjectPipelinesOptions, repo interface{}) ([]*gitlab.PipelineInfo, error) { if client == nil { client = apiClient } + if l.PerPage == 0 { + l.PerPage = DefaultListLimit + } + pipes, _, err := client.Pipelines.ListProjectPipelines(repo, l) if err != nil { return nil, err @@ -249,9 +253,9 @@ var PipelineJobsWithSha = func(client *gitlab.Client, pid interface{}, sha strin if client == nil { client = apiClient } - pipelines, _, err := client.Pipelines.ListProjectPipelines(pid, &gitlab.ListProjectPipelinesOptions{ + pipelines, err := GetPipelines(client, &gitlab.ListProjectPipelinesOptions{ SHA: gitlab.String(sha), - }) + }, pid) if len(pipelines) == 0 || err != nil { return nil, err } diff --git a/pkg/api/release.go b/pkg/api/release.go index f00e681f..dfde1c96 100644 --- a/pkg/api/release.go +++ b/pkg/api/release.go @@ -19,6 +19,10 @@ var ListReleases = func(client *gitlab.Client, projectID interface{}, opts *gitl client = apiClient } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + releases, _, err := client.Releases.ListReleases(projectID, opts) if err != nil { return nil, err diff --git a/pkg/api/user.go b/pkg/api/user.go index a77cef07..284b389b 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -19,6 +19,11 @@ var CurrentUser = func(client *gitlab.Client) (*gitlab.User, error) { var UserByName = func(client *gitlab.Client, name string) (*gitlab.User, error) { opts := &gitlab.ListUsersOptions{Username: gitlab.String(name)} + + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + users, _, err := apiClient.Users.ListUsers(opts) if err != nil { return nil, err diff --git a/pkg/tableprinter/table_printer.go b/pkg/tableprinter/table_printer.go new file mode 100644 index 00000000..ca39571f --- /dev/null +++ b/pkg/tableprinter/table_printer.go @@ -0,0 +1,168 @@ +package tableprinter + +import ( + "fmt" + + "github.com/gosuri/uitable" +) + +var ( + DefaultSeparator = " " + DefaultMaxColWidth uint = 70 +) + +// TablePrinter represents a decorator that renders the data formatted in a tabular form. +type TablePrinter struct { + // List of columns to display + Header []string + // Total number of records. Needed if AddRowFunc is used + TotalRows int + // Wrap when set to true wraps the contents of the columns when the length exceeds the MaxColWidth + Wrap bool + // MaxColWidth is the maximum allowed width for cells in the table + MaxColWidth uint + // Separator is the seperator for columns in the table. Default is " " + Separator string + // Rows is the collection of rows in the table + Rows [][]*TableCell +} + +type TableCell struct { + Text interface{} +} + +func NewTablePrinter() TablePrinter { + return TablePrinter{ + Separator: DefaultSeparator, + MaxColWidth: DefaultMaxColWidth, + Wrap: false, + } +} + +func (t *TablePrinter) AddCell(s interface{}) { + if t.Rows == nil { + t.Rows = make([][]*TableCell, 1) + } + rowI := len(t.Rows) - 1 + field := TableCell{ + Text: s, + } + t.Rows[rowI] = append(t.Rows[rowI], &field) +} + +func (t *TablePrinter) appendCellToIndex(s interface{}, index int) { + if t.Rows == nil { + t.Rows = make([][]*TableCell, 1) + } + rowI := len(t.Rows) - 1 + last := len(t.Rows[rowI]) - 1 + + if last <= index { + t.AddCell(s) + return + } + + t.Rows[rowI] = append(t.Rows[rowI], t.Rows[rowI][last]) + copy(t.Rows[rowI][(index+1):], t.Rows[rowI][index:last]) + + field := TableCell{ + Text: s, + } + t.Rows[rowI][index] = &field +} + +// AddCellf formats according to a format specifier and adds cell to row +func (t *TablePrinter) AddCellf(s string, f ...interface{}) { + t.AddCell(fmt.Sprintf(s, f...)) +} + +func (t *TablePrinter) AddRow(str ...interface{}) { + for _, s := range str { + t.AddCell(s) + } + t.EndRow() +} + +// AddCellI appends a cell to the given index +func (t *TablePrinter) AddCellI(index int, str interface{}) { + t.appendCellToIndex(str, index) +} + +func (t *TablePrinter) AddRowFunc(f func(int, int) string) { + for ri := 0; ri < t.TotalRows; ri++ { + row := make([]interface{}, t.TotalRows) + for ci := range row { + row[ci] = f(ri, ci) + } + t.AddRow(row) + t.EndRow() + } +} + +func (t *TablePrinter) EndRow() { + t.Rows = append(t.Rows, []*TableCell{}) +} + +// Bytes returns the []byte value of table +func (t *TablePrinter) Bytes() []byte { + return []byte(t.String()) +} + +// String returns the string value of table. Alternative to Render() +func (t *TablePrinter) String() string { + return t.Render() +} + +// purgeRow removes empty rows +func (t *TablePrinter) purgeRow() { + newSlice := make([][]*TableCell, 0, len(t.Rows)) + for _, item := range t.Rows { + if len(item) > 0 { + newSlice = append(newSlice, item) + } + } + t.Rows = newSlice +} + +// Render builds and returns the string representation of the table +func (t *TablePrinter) Render() string { + table := uitable.New() + + if t.MaxColWidth != 0 { + table.MaxColWidth = t.MaxColWidth + } + + if t.Separator != "" { + table.Separator = t.Separator + } + + t.purgeRow() // remove empty rows + rLen := len(t.Rows) + + if rLen > 0 { + if len(t.Header) > 0 { + header := make([]interface{}, len(t.Header)) + for ci, c := range t.Header { + header[ci] = c + } + + table.AddRow(header...) + } + + for _, row := range t.Rows { + rowData := make([]interface{}, len(row)) + for i, r := range row { + if len(rowData) <= i { + rowData[i-1] = r.Text + continue + } + rowData[i] = r.Text + } + table.AddRow(rowData...) + } + + return table.String() + } + + return "" +}