Added todos list command

Added a new command to list open todos

https://gitlab.com/gitlab-org/cli/-/issues/1114
This commit is contained in:
Phil Hughes 2023-10-05 15:57:14 +01:00
parent 2de2d082c8
commit 5acdce7577
No known key found for this signature in database
GPG Key ID: 3C5BB067AE768629
11 changed files with 387 additions and 1 deletions

19
api/todo.go Normal file
View File

@ -0,0 +1,19 @@
package api
import "github.com/xanzy/go-gitlab"
var ListTodos = func(client *gitlab.Client, opts *gitlab.ListTodosOptions) ([]*gitlab.Todo, *gitlab.Response, error) {
if client == nil {
client = apiClient.Lab()
}
if opts.PerPage == 0 {
opts.PerPage = DefaultListLimit
}
todos, resp, err := client.Todos.ListTodos(opts)
if err != nil {
return nil, nil, err
}
return todos, resp, nil
}

View File

@ -26,6 +26,7 @@ import (
scheduleCmd "gitlab.com/gitlab-org/cli/commands/schedule"
snippetCmd "gitlab.com/gitlab-org/cli/commands/snippet"
sshCmd "gitlab.com/gitlab-org/cli/commands/ssh-key"
todoCmd "gitlab.com/gitlab-org/cli/commands/todo"
updateCmd "gitlab.com/gitlab-org/cli/commands/update"
userCmd "gitlab.com/gitlab-org/cli/commands/user"
variableCmd "gitlab.com/gitlab-org/cli/commands/variable"
@ -126,6 +127,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command {
rootCmd.AddCommand(scheduleCmd.NewCmdSchedule(f))
rootCmd.AddCommand(snippetCmd.NewCmdSnippet(f))
rootCmd.AddCommand(askCmd.NewCmd(f))
rootCmd.AddCommand(todoCmd.NewCmdTodo(f))
rootCmd.Flags().BoolP("version", "v", false, "show glab version information")
return rootCmd

View File

@ -0,0 +1,109 @@
package list
import (
"fmt"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"gitlab.com/gitlab-org/cli/pkg/tableprinter"
"gitlab.com/gitlab-org/cli/pkg/utils"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
"gitlab.com/gitlab-org/cli/commands/todo/todoutils"
)
type ListOptions struct {
State string
// Pagination
Page int
PerPage int
// display opts
TitleQualifier string
IO *iostreams.IOStreams
HTTPClient func() (*gitlab.Client, error)
}
func DisplayAllTodos(streams *iostreams.IOStreams, todos []*gitlab.Todo) string {
table := tableprinter.NewTablePrinter()
table.SetIsTTY(streams.IsOutputTTY())
for _, todo := range todos {
table.AddCell(todoutils.TodoActionName(todo))
table.AddCell(streams.Hyperlink(fmt.Sprintf("%s%s", todo.Project.PathWithNamespace, todo.Target.Reference), todo.TargetURL))
table.AddCell(todo.Body)
table.EndRow()
}
return table.Render()
}
func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error) *cobra.Command {
opts := &ListOptions{
IO: f.IO,
}
todoListCmd := &cobra.Command{
Use: "list [flags]",
Short: `List your todos`,
Long: ``,
Aliases: []string{"ls"},
Example: heredoc.Doc(`
glab todo list
glab todo list --state done
`),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
opts.HTTPClient = f.HttpClient
apiClient, err := opts.HTTPClient()
if err != nil {
return err
}
l := &gitlab.ListTodosOptions{}
if p, _ := cmd.Flags().GetInt("page"); p != 0 {
opts.Page = p
l.Page = p
}
if p, _ := cmd.Flags().GetInt("per-page"); p != 0 {
opts.PerPage = p
l.PerPage = p
}
if state, _ := cmd.Flags().GetString("state"); state != "" {
opts.State = state
l.State = gitlab.String(state)
}
title := utils.NewListTitle(opts.TitleQualifier + " todo")
todos, _, err := api.ListTodos(apiClient, l)
if err != nil {
return err
}
title.Page = l.Page
title.CurrentPageTotal = len(todos)
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), DisplayAllTodos(opts.IO, todos))
return nil
},
}
todoListCmd.Flags().IntP("page", "p", 1, "Page number")
todoListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page")
todoListCmd.Flags().StringP("state", "s", "pending", "State of todo")
return todoListCmd
}

View File

@ -0,0 +1,79 @@
package list
import (
"net/http"
"testing"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/MakeNowJust/heredoc"
"github.com/stretchr/testify/assert"
"gitlab.com/gitlab-org/cli/commands/cmdtest"
"gitlab.com/gitlab-org/cli/pkg/httpmock"
"gitlab.com/gitlab-org/cli/test"
)
func runCommand(rt http.RoundTripper) (*test.CmdOut, error) {
ios, _, stdout, stderr := iostreams.Test()
factory := cmdtest.InitFactory(ios, rt)
_, _ = factory.HttpClient()
cmd := NewCmdList(factory, nil)
_, err := cmd.ExecuteC()
return &test.CmdOut{
OutBuf: stdout,
ErrBuf: stderr,
}, err
}
func TestTodoList(t *testing.T) {
fakeHTTP := &httpmock.Mocker{}
defer fakeHTTP.Verify(t)
fakeHTTP.RegisterResponder(http.MethodGet, "/api/v4/todos",
httpmock.NewStringResponse(http.StatusOK, `
[
{
"id": 102,
"project": {
"path_with_namespace": "gitlab-org/gitlab-foss"
},
"action_name": "marked",
"target": {
"reference": "!1"
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-foss/-/merge_requests/7"
},
{
"id": 102,
"project": {
"path_with_namespace": "gitlab-org/gitlab-foss"
},
"action_name": "build_failed",
"target": {
"reference": "!1"
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-foss/-/merge_requests/7"
}
]
`))
output, err := runCommand(fakeHTTP)
if err != nil {
t.Errorf("error running command `todo list`: %v", err)
}
out := output.String()
assert.Equal(t, heredoc.Doc(`
Showing 2 todos (Page 1)
Added todo gitlab-org/gitlab-foss!1
Pipeline failed gitlab-org/gitlab-foss!1
`), out)
assert.Empty(t, output.Stderr())
}

18
commands/todo/todo.go Normal file
View File

@ -0,0 +1,18 @@
package todo
import (
"github.com/spf13/cobra"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
todoListCmd "gitlab.com/gitlab-org/cli/commands/todo/list"
)
func NewCmdTodo(f *cmdutils.Factory) *cobra.Command {
todoCmd := &cobra.Command{
Use: "todo <command> [flags]",
Short: `List todos`,
Long: ``,
}
todoCmd.AddCommand(todoListCmd.NewCmdList(f, nil))
return todoCmd
}

View File

@ -0,0 +1,32 @@
package todo
import (
"bytes"
"io"
"os"
"testing"
"github.com/stretchr/testify/assert"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
)
func TestNewCmdTodo(t *testing.T) {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
assert.Nil(t, NewCmdTodo(&cmdutils.Factory{}).Execute())
outC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = old
out := <-outC
assert.Contains(t, out, " \"todo [command]")
}

View File

@ -0,0 +1,29 @@
package todoutils
import (
"github.com/xanzy/go-gitlab"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func TodoActionName(todo *gitlab.Todo) string {
switch todo.ActionName {
case "approval_required":
return "Approval required"
case "build_failed":
return "Pipeline failed"
case "directly_addressed":
return "Mentioned"
case "marked":
return "Added todo"
case "merge_train_removed":
return "Removed from merge train"
case "review_requested":
return "Review requested"
case "review_submitted":
return "Review submitted"
default:
return cases.Title(language.English, cases.NoLower).String(string(todo.ActionName))
}
}

24
docs/source/todo/help.md Normal file
View File

@ -0,0 +1,24 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab todo help`
Help about any command
```plaintext
glab todo help [command] [flags]
```
## Options inherited from parent commands
```plaintext
--help Show help for command
```

24
docs/source/todo/index.md Normal file
View File

@ -0,0 +1,24 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab todo`
List todos
## Options inherited from parent commands
```plaintext
--help Show help for command
```
## Subcommands
- [`list`](list.md)

46
docs/source/todo/list.md Normal file
View File

@ -0,0 +1,46 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab todo list`
List your todos
```plaintext
glab todo list [flags]
```
## Aliases
```plaintext
ls
```
## Examples
```plaintext
glab todo list
glab todo list --state done
```
## Options
```plaintext
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
-s, --state string State of todo (default "pending")
```
## Options inherited from parent commands
```plaintext
--help Show help for command
```

View File

@ -67,7 +67,11 @@ func (opts *ListTitleOptions) Describe() string {
}
if opts.CurrentPageTotal > 0 {
return fmt.Sprintf("Showing %s %s on %s %s\n", pageNumInfo, opts.Name, opts.RepoName, pageInfo)
if opts.RepoName == "" {
return fmt.Sprintf("Showing %s %s %s\n", pageNumInfo, opts.Name, pageInfo)
} else {
return fmt.Sprintf("Showing %s %s on %s %s\n", pageNumInfo, opts.Name, opts.RepoName, pageInfo)
}
}
emptyMessage := opts.EmptyMessage