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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fmt: ## format source with gofumpt
.PHONY: lint
lint: ## lint source
@echo "Check for golangci-lint"; [ -e "$(shell which golangci-lint)" ]
@echo "Executing golangci-lint"; golangci-lint run -v --timeout $(TIMEOUT) ./internal
@echo "Executing golangci-lint"; golangci-lint run -v --timeout $(TIMEOUT) ./internal/...

.PHONY: test
test: ## run unit tests with gotestsum
Expand Down
1 change: 1 addition & 0 deletions docs/azdo_boards_area.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Manage area paths used by Azure Boards.
### Available commands

* [azdo boards area project](./azdo_boards_area_project.md)
* [azdo boards area team](./azdo_boards_area_team.md)

### ALIASES

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

Manage area paths scoped to a team.

### Available commands

* [azdo boards area team list](./azdo_boards_area_team_list.md)

### ALIASES

- `t`

### Examples

```bash
# List team area paths for a project in the default organization
azdo boards area team list Fabrikam/"My Team"

# List team area paths for a project in a specific organization
azdo boards area team list myOrg/Fabrikam/"My Team"
```

### See also

* [azdo boards area](./azdo_boards_area.md)
50 changes: 50 additions & 0 deletions docs/azdo_boards_area_team_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
## Command `azdo boards area team list`

```
azdo boards area team list [ORGANIZATION/]PROJECT/TEAM [flags]
```

List Azure Boards area paths assigned to a team. The TEAM argument accepts
the ID (GUID) or name of the team. The argument accepts the form
[ORGANIZATION/]PROJECT/TEAM. When the organization segment is omitted,
the default organization from configuration is used.


### 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.

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

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


### ALIASES

- `ls`
- `l`

### JSON Fields

`areaPath`, `includeChildren`, `isDefault`

### Examples

```bash
# List area paths for a team using the default organization
azdo boards area team list Fabrikam/"Fabrikam Engineering"

# List area paths for a team in a specific organization
azdo boards area team list MyOrg/Fabrikam/"My Team"
```

### See also

* [azdo boards area team](./azdo_boards_area_team.md)
26 changes: 26 additions & 0 deletions docs/azdo_help_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@ Aliases
ls, l
```

#### `azdo boards area team <command>`

Manage area paths scoped to a team.

Aliases

```
t
```

##### `azdo boards area team list [ORGANIZATION/]PROJECT/TEAM [flags]`

List area paths assigned to a team.

```
-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.
-t, --template string Format JSON output using a Go template; see "azdo help formatting"
```

Aliases

```
ls, l
```

### `azdo boards iteration <command>`

Work with iteration/classification nodes.
Expand Down
2 changes: 2 additions & 0 deletions internal/azdo/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/security"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/serviceendpoint"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/taskagent"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/work"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/workitemtracking"
"github.com/tmeckel/azdo-cli/internal/azdo/extensions"
)
Expand Down Expand Up @@ -59,5 +60,6 @@ type ClientFactory interface {
Security(ctx context.Context, organization string) (security.Client, error)
TaskAgent(ctx context.Context, organization string) (taskagent.Client, error)
Extensions(ctx context.Context, organization string) (extensions.Client, error)
Work(ctx context.Context, organization string) (work.Client, error)
WorkItemTracking(ctx context.Context, organization string) (workitemtracking.Client, error)
}
9 changes: 9 additions & 0 deletions internal/azdo/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/security"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/serviceendpoint"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/taskagent"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/work"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/workitemtracking"
"github.com/tmeckel/azdo-cli/internal/azdo/extensions"
"github.com/tmeckel/azdo-cli/internal/config"
Expand Down Expand Up @@ -146,6 +147,14 @@ func (c *clientFactory) Extensions(ctx context.Context, org string) (extensions.
return extensions.NewClient(ctx, conn.(*connectionAdapter).conn), nil
}

func (c *clientFactory) Work(ctx context.Context, org string) (work.Client, error) {
conn, err := c.factory.Connection(org)
if err != nil {
return nil, err
}
return work.NewClient(ctx, conn.(*connectionAdapter).conn)
}

func (c *clientFactory) WorkItemTracking(ctx context.Context, org string) (workitemtracking.Client, error) {
conn, err := c.factory.Connection(org)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions internal/cmd/boards/area/area.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package area
import (
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra"
projectcmd "github.com/tmeckel/azdo-cli/internal/cmd/boards/area/project"
"github.com/tmeckel/azdo-cli/internal/cmd/boards/area/project"
"github.com/tmeckel/azdo-cli/internal/cmd/boards/area/team"
"github.com/tmeckel/azdo-cli/internal/cmd/util"
)

Expand All @@ -21,7 +22,8 @@ func NewCmd(ctx util.CmdContext) *cobra.Command {
},
}

cmd.AddCommand(projectcmd.NewCmd(ctx))
cmd.AddCommand(project.NewCmd(ctx))
cmd.AddCommand(team.NewCmd(ctx))

return cmd
}
148 changes: 148 additions & 0 deletions internal/cmd/boards/area/team/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package list

import (
"fmt"
"sort"
"strings"

"github.com/MakeNowJust/heredoc/v2"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/work"
"github.com/spf13/cobra"

"github.com/tmeckel/azdo-cli/internal/cmd/util"
"github.com/tmeckel/azdo-cli/internal/types"
)

type listOptions struct {
targetArg string
exporter util.Exporter
}

type teamFieldValueView struct {
AreaPath string `json:"areaPath"`
IncludeChildren bool `json:"includeChildren"`
IsDefault bool `json:"isDefault"`
}

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

cmd := &cobra.Command{
Use: "list [ORGANIZATION/]PROJECT/TEAM",
Short: "List area paths assigned to a team.",
Long: heredoc.Doc(`
List Azure Boards area paths assigned to a team. The TEAM argument accepts
the ID (GUID) or name of the team. The argument accepts the form
[ORGANIZATION/]PROJECT/TEAM. When the organization segment is omitted,
the default organization from configuration is used.
`),
Example: heredoc.Doc(`
# List area paths for a team using the default organization
azdo boards area team list Fabrikam/"Fabrikam Engineering"

# List area paths for a team in a specific organization
azdo boards area team list MyOrg/Fabrikam/"My Team"
`),
Aliases: []string{"ls", "l"},
Args: util.ExactArgs(1, "team argument required"),
RunE: func(cmd *cobra.Command, args []string) error {
opts.targetArg = args[0]
return runList(ctx, opts)
},
}

util.AddJSONFlags(cmd, &opts.exporter, []string{"areaPath", "includeChildren", "isDefault"})

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.ParseProjectTargetWithDefaultOrganization(ctx, opts.targetArg)
if err != nil {
return util.FlagErrorWrap(err)
}

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

teamFieldValues, err := client.GetTeamFieldValues(ctx.Context(), work.GetTeamFieldValuesArgs{
Project: &scope.Project,
Team: &scope.Targets[0],
})
if err != nil {
return fmt.Errorf("failed to fetch team field values: %w", err)
}

ios.StopProgressIndicator()

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

return renderTable(ctx, teamFieldValues)
}

func buildView(tfv *work.TeamFieldValues) []teamFieldValueView {
if tfv == nil || tfv.Values == nil {
return nil
}

defaultValue := types.GetValue(tfv.DefaultValue, "")

views := make([]teamFieldValueView, 0, len(*tfv.Values))
for _, v := range *tfv.Values {
areaPath := types.GetValue(v.Value, "")
views = append(views, teamFieldValueView{
AreaPath: areaPath,
IncludeChildren: types.GetValue(v.IncludeChildren, false),
IsDefault: strings.EqualFold(areaPath, defaultValue),
})
}

sort.Slice(views, func(i, j int) bool {
return strings.ToLower(views[i].AreaPath) < strings.ToLower(views[j].AreaPath)
})

return views
}

func renderTable(ctx util.CmdContext, tfv *work.TeamFieldValues) error {
views := buildView(tfv)
if len(views) == 0 {
return util.NewNoResultsError("no team area paths found")
}

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

tp.AddColumns("AREA PATH", "INCLUDE SUB AREAS")
tp.EndRow()

for _, v := range views {
label := v.AreaPath
if v.IsDefault {
label += " (default)"
}
tp.AddField(label)
if v.IncludeChildren {
tp.AddField("yes")
} else {
tp.AddField("no")
}
tp.EndRow()
}

return tp.Render()
}
Loading
Loading