Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/azdo_help_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,26 @@ Aliases
c, cr, new, n, add, a
```

### `azdo team list [ORGANIZATION/]PROJECT [flags]`

List teams in a project.

```
-q, --jq expression Filter JSON output using a jq expression
--json fields[=*] Output JSON with the specified fields. Prefix a field with '-' to exclude it.
--max-items int Maximum number of teams to return across all pages (client-side; 0 = unlimited)
--mine Return only teams the current user is a member of
--skip int Number of teams to skip (server-side)
-t, --template string Format JSON output using a Go template; see "azdo help formatting"
--top int Maximum number of teams to return per page (server-side; 0 = server default)
```

Aliases

```
ls, l
```



### See also
Expand Down
1 change: 1 addition & 0 deletions docs/azdo_team.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Manage Azure DevOps teams.
### Available commands

* [azdo team create](./azdo_team_create.md)
* [azdo team list](./azdo_team_list.md)

### ALIASES

Expand Down
67 changes: 67 additions & 0 deletions docs/azdo_team_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Command `azdo team list`

```
azdo team list [ORGANIZATION/]PROJECT [flags]
```

List all teams in the specified project. Supports server-side paging via
--top and --skip, --mine filtering, and JSON export.


### Options


* `-q`, `--jq` `expression`

Filter JSON output using a jq expression

* `--json` `fields`

Output JSON with the specified fields. Prefix a field with '-' to exclude it.

* `--max-items` `int` (default `0`)

Maximum number of teams to return across all pages (client-side; 0 = unlimited)

* `--mine`

Return only teams the current user is a member of

* `--skip` `int` (default `0`)

Number of teams to skip (server-side)

* `-t`, `--template` `string`

Format JSON output using a Go template; see "azdo help formatting"

* `--top` `int` (default `0`)

Maximum number of teams to return per page (server-side; 0 = server default)


### ALIASES

- `ls`
- `l`

### JSON Fields

`description`, `id`, `identity`, `identityUrl`, `name`, `projectId`, `projectName`, `url`

### Examples

```bash
# List all teams in the default organization
azdo team list Fabrikam

# List the first 10 teams in a specific organization
azdo team list MyOrg/Fabrikam --top 10

# List teams you are a member of
azdo team list Fabrikam --mine
```

### See also

* [azdo team](./azdo_team.md)
170 changes: 170 additions & 0 deletions internal/cmd/team/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package list

import (
"fmt"
"sort"
"strings"

"github.com/MakeNowJust/heredoc/v2"
"github.com/google/uuid"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core"
"github.com/spf13/cobra"
"github.com/tmeckel/azdo-cli/internal/cmd/util"
"github.com/tmeckel/azdo-cli/internal/types"
)

type listOptions struct {
scopeArg string
top int
skip int
mine bool
maxItems int
exporter util.Exporter
}

func NewCmd(ctx util.CmdContext) *cobra.Command {
opts := &listOptions{}

cmd := &cobra.Command{
Use: "list [ORGANIZATION/]PROJECT",
Short: "List teams in a project.",
Long: heredoc.Doc(`
List all teams in the specified project. Supports server-side paging via
--top and --skip, --mine filtering, and JSON export.
`),
Example: heredoc.Doc(`
# List all teams in the default organization
azdo team list Fabrikam

# List the first 10 teams in a specific organization
azdo team list MyOrg/Fabrikam --top 10

# List teams you are a member of
azdo team list Fabrikam --mine
`),
Aliases: []string{"ls", "l"},
Args: util.ExactArgs(1, "project argument required"),
RunE: func(cmd *cobra.Command, args []string) error {
opts.scopeArg = args[0]
return runList(ctx, opts)
},
}

cmd.Flags().IntVar(&opts.top, "top", 0, "Maximum number of teams to return per page (server-side; 0 = server default)")
cmd.Flags().IntVar(&opts.skip, "skip", 0, "Number of teams to skip (server-side)")
cmd.Flags().BoolVar(&opts.mine, "mine", false, "Return only teams the current user is a member of")
cmd.Flags().IntVar(&opts.maxItems, "max-items", 0, "Maximum number of teams to return across all pages (client-side; 0 = unlimited)")

util.AddJSONFlags(cmd, &opts.exporter, []string{
"id", "name", "description", "url",
"identity", "identityUrl", "projectId", "projectName",
})

return cmd
}

func runList(ctx util.CmdContext, opts *listOptions) error {
ios, err := ctx.IOStreams()
if err != nil {
return err
}

ios.StartProgressIndicator()
defer ios.StopProgressIndicator()

scope, err := util.ParseProjectScope(ctx, opts.scopeArg)
if err != nil {
return util.FlagErrorWrap(err)
}

client, err := ctx.ClientFactory().Core(ctx.Context(), scope.Organization)
if err != nil {
return fmt.Errorf("failed to create Core client: %w", err)
}

teams, err := fetchTeams(ctx, client, scope.Project, opts)
if err != nil {
return err
}

ios.StopProgressIndicator()

if opts.exporter != nil {
return opts.exporter.Write(ios, teams)
}

return renderTeamsTable(ctx, teams)
}

func fetchTeams(ctx util.CmdContext, client core.Client, project string, opts *listOptions) ([]core.WebApiTeam, error) {
if opts.maxItems < 0 {
return nil, util.FlagErrorf("--max-items must be >= 0")
}

out := make([]core.WebApiTeam, 0)
skip := opts.skip

for {
args := core.GetTeamsArgs{ProjectId: &project}
if opts.mine {
mine := true
args.Mine = &mine
}
if opts.top > 0 {
top := opts.top
args.Top = &top
}
if skip > 0 {
s := skip
args.Skip = &s
}

resp, err := client.GetTeams(ctx.Context(), args)
if err != nil {
return nil, fmt.Errorf("failed to list teams: %w", err)
}
if resp == nil || len(*resp) == 0 {
return out, nil
}

for _, t := range *resp {
out = append(out, t)
if opts.maxItems > 0 && len(out) >= opts.maxItems {
return out, nil
}
}

if opts.top > 0 && len(*resp) < opts.top {
return out, nil
}

skip += opts.top
if opts.top == 0 {
return out, nil
}
}
}

func renderTeamsTable(ctx util.CmdContext, teams []core.WebApiTeam) error {
sort.Slice(teams, func(i, j int) bool {
return strings.ToLower(types.GetValue(teams[i].Name, "")) < strings.ToLower(types.GetValue(teams[j].Name, ""))
})

tp, err := ctx.Printer("list")
if err != nil {
return err
}

tp.AddColumns("ID", "NAME", "DESCRIPTION", "PROJECT")
tp.EndRow()

for _, t := range teams {
tp.AddField(types.GetValue(t.Id, uuid.UUID{}).String())
tp.AddField(types.GetValue(t.Name, ""))
tp.AddField(types.GetValue(t.Description, ""))
tp.AddField(types.GetValue(t.ProjectName, ""))
tp.EndRow()
}

return tp.Render()
}
Loading
Loading