Skip to content

feat: Implement azdo pipelines queue show command #245

@tmeckel

Description

@tmeckel

Sub-issue of #240. Hardened spec — do not re-derive decisions. Mirrors internal/cmd/pr/view/view.go and uses Go text templates via internal/template.Template (the same engine used by azdo pr view).

Show commands have no table output. This command renders a single queue via Go text template (default) or --json (opt-in). Table output is reserved for list commands.

Command Description

Display the details of a single Azure DevOps agent queue by integer ID or name. The command resolves the target (positive integer is used directly; a string is resolved via GetAgentQueues), fetches the matching TaskAgentQueue via the Agent Queues REST 7.1 endpoint, and renders it as a Go text template. The queue's Pool (the agent pool it references) is rendered with its name and ID when present.

GET https://dev.azure.com/{organization}/{project}/_apis/distributedtask/queues/{queueId}?api-version=7.1

Locked Decisions (do not re-derive)

# Decision Rationale
1 Use the vendored SDK taskagent.Client.GetAgentQueue (not raw HTTP). Mock already generated at internal/mocks/taskagent_client_mock.go:456-471. Consistent with the other pool/queue subgroup siblings; the SDK is what the umbrella wires in #240.
2 The queue is identified by a positional QUEUE argument ([ORGANIZATION/]PROJECT/QUEUE). The target segment is resolved via a new shared.ResolveQueue helper at internal/cmd/pipelines/queue/show/shared/resolve.go: if it parses as a positive integer, use it directly; otherwise call taskagent.Client.GetAgentQueues(queueName=...) and pick the first match. Folds the previous --id flag into the positional. Mirrors the variablegroup/delete resolution pattern. Queues are project-scoped.
2.5 Parse the positional using util.ParseProjectTargetWithDefaultOrganization from internal/cmd/util/scope.go:183. The function returns a *Target with Organization, Project, Target fields, accepting 2- or 3-segment inputs. Mirrors internal/cmd/pipelines/variablegroup/{create,delete,show,update}/ precedent.
3 util.ExactArgs(1, "queue target is required"). Standard cobra pattern; the 1st positional is the full target.
4 Use the Go text template engine from internal/template/template.go with an //go:embed show.tpl file. Mirror internal/cmd/pr/view/view.go and view.tpl structure: bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender. The user explicitly requested template rendering like pr/view.
5 Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]PROJECT/QUEUE", cmd.Aliases: []string{"view", "status"}. Mirrors the pr/view aliasing pattern but with show as the primary.
6 JSON output passes the raw SDK *taskagent.TaskAgentQueue to opts.exporter.Write. No view struct. Symmetric with other show siblings.
7 No confirmation prompt. Show is read-only. Show is non-destructive.
8 --raw flag dumps the full SDK queue with spew.Dump to stderr for debugging. Mirrors pr/view --raw.
9 No new SDK client, no new helper beyond shared.ResolveQueue, no new package beyond internal/cmd/pipelines/queue/show. Reuse SDK call from the vendored taskagent package. Mandate: minimal code.
10 Mock for GetAgentQueue is already generated at internal/mocks/taskagent_client_mock.go:456-471. Do not regenerate. Verified.
11 taskagent.Client.GetAgentQueues is used for name resolution; mock also generated. Mirrors the variablegroup/delete name-resolution pattern.

Command Signature

azdo pipelines queue show [ORGANIZATION/]PROJECT/QUEUE
  [--raw]
  [--json ...]
  • Aliases: view, status
  • Positional parsing: args[0] → target (via util.ParseProjectTargetWithDefaultOrganization).
  • The Target field of the parsed *Target is resolved via shared.ResolveQueue(ctx, clientFact, args[0]).

Flags

Flag Maps to Notes
--raw (bool) debug dump spew.Dump of full SDK queue to stderr
--json / --jq / --template util.AddJSONFlags JSON export of raw SDK queue

JSON Output Contract

util.AddJSONFlags(cmd, &opts.exporter, []string{
    "id", "name", "pool", "projectId", "url", "_links",
    "createdBy", "createdDate", "lastChangedBy", "lastChangedDate",
})

Pass the raw *taskagent.TaskAgentQueue returned by the SDK (Decision 6).

Template Output Contract (show.tpl)

The default template renders (see internal/cmd/pr/view/view.tpl for the established pattern):

url:           
id:            
name:          
project id:    
pool:          ()  (id and name when present)
created on:    
created by:    ()  (if present)
last changed on: ()  (if present)
last changed by: ()  (if present)

Command Wiring

  • Package path: internal/cmd/pipelines/queue/show
  • Files:
    • show.goNewCmd(ctx util.CmdContext) *cobra.Command + showOptions + runShow
    • show.tpl — Go text template
    • shared/resolve.goResolveQueue(ctx, clientFact, raw) (int, error) (positive-int fast path + GetAgentQueues first-match lookup)
    • show_test.go — table-driven gomock tests
  • Update internal/cmd/pipelines/queue/queue.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/queue/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
  • Existing higher-level parents must already remain wired: pipelinesqueueshow.

API Surface

Reuse the already-vendored client. No new SDK clients required.

  • taskagent.Client.GetAgentQueueAgent Queues - Get (REST 7.1)
  • taskagent.GetAgentQueueArgs struct: {Project *string, QueueId *int}.
  • taskagent.Client.GetAgentQueues (and GetAgentQueuesArgs with Project *string, QueueName *string) — for name → ID resolution.
  • Template engine: internal/template.Template with bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender.

Mock for GetAgentQueue is already generated at internal/mocks/taskagent_client_mock.go:456-471. No mock regeneration needed.

Implementation Approach (TDD, reuse-first, minimal)

Phase 1 — RED (tests first). Mirror setupFakeDeps from internal/cmd/boards/workitem/list/list_test.go:765-844. Add show_test.go with the following table-driven / behaviour tests, all using t.Parallel() and gomock (require for preconditions, assert for verifications):

  • TestNewCmd_RegistersAsShowLeaf — asserts cmd.Name() == "show", cmd.Aliases contains view and status, cmd.Use starts with show [ORGANIZATION/]PROJECT/QUEUE.
  • TestNewCmd_RequiresOneArg — runs cmd.SetArgs([]string{}) + cmd.Execute(); asserts cobra ExactArgs error.
  • TestRunShow_ResolveByPositiveInteger — sets Fabrikam/7; asserts shared.ResolveQueue returns 7 without calling GetAgentQueues.
  • TestRunShow_ResolveByName — sets Fabrikam/Default; stubs taskagent.EXPECT().GetAgentQueues(...); asserts *args.QueueId == 7 (resolved ID).
  • TestRunShow_BasicCall — sets Fabrikam/7; stubs taskagent.EXPECT().GetAgentQueue(gomock.Any(), gomock.Any()); asserts *args.QueueId == 7, *args.Project == "Fabrikam".
  • TestRunShow_TemplateOutput_BasicFields — mocks return *TaskAgentQueue{Id, Name, ProjectId, Url}; asserts rendered output contains all field labels and values.
  • TestRunShow_TemplateOutput_Hyperlink — asserts the url: line uses ANSI hyperlink escape sequence.
  • TestRunShow_TemplateOutput_Pool_Nested — mocks return Pool: &TaskAgentPoolReference{Id, Name}; asserts pool: 7 (Default) rendered (or pool: 7 if name missing).
  • TestRunShow_TemplateOutput_NoPool — mocks return Pool: nil; asserts pool: line is omitted.
  • TestRunShow_TemplateOutput_CreatedBy_Nested — mocks return CreatedBy: &IdentityRef{DisplayName, UniqueName}; asserts created by: Alice (alice@contoso.com) rendered.
  • TestRunShow_TemplateOutput_NoCreatedBy — mocks return CreatedBy: nil; asserts created by: line is omitted.
  • TestRunShow_TemplateOutput_LastChangedBy_Nested — mocks return LastChangedBy: &IdentityRef{...}; asserts last changed by: rendered.
  • TestRunShow_TemplateOutput_NoLastChangedBy — mocks return LastChangedBy: nil; asserts last changed by: line is omitted.
  • TestRunShow_JSONOutput — sets --json; mocks return queue; asserts JSON contains id, name, pool, projectId, url, createdBy, createdDate, lastChangedBy, lastChangedDate.
  • TestRunShow_RawFlag — sets --raw; asserts spew.Dump was invoked.
  • TestRunShow_ProjectScopeParsing — table-driven: myorg/Fabrikam/7, Fabrikam/7, invalid ("org/proj/extra/x"), empty.
  • TestRunShow_InvalidProjectScope — asserts util.FlagErrorWrap returned.
  • TestRunShow_ClientFactoryError — stubs factory to return error; asserts wrapped error.
  • TestRunShow_SDKError — stubs SDK to return error; asserts wrapped error.
  • TestRunShow_OrganizationFromConfigDefault — when scopeArg is Fabrikam/7 (no org), asserts clientFact.TaskAgent(ctx, defaultOrg) is called with the configured default.

Phase 2 — GREEN (minimal implementation). Strict reuse rules:

  • No new helpers beyond shared.ResolveQueue(ctx, clientFact, raw) (int, error) (~30 lines): positive-int fast path + taskagent.Client.GetAgentQueues first-match lookup; error on zero/ambiguous matches.
  • Reuse util.ParseProjectTargetWithDefaultOrganization, util.AddJSONFlags, util.FlagErrorf/FlagErrorWrap, types.GetValue, types.ToPtr, ios.StartProgressIndicator/StopProgressIndicator, iostreams.Test as-is.
  • Reuse internal/template.New(...).WithFuncs(...).Parse(show.tpl).ExecuteData(data) — exact same pattern as internal/cmd/pr/view/view.go:483-549.
  • Progress indicator: ios.StartProgressIndicator() + defer ios.StopProgressIndicator(); call ios.StopProgressIndicator() immediately before template execution.
  • Output split: JSON via opts.exporter.Write(ios, res) passing the raw SDK *TaskAgentQueue; template via template.New(...).ExecuteData(templateData{Queue: res}).
  • Debug log at the point of the SDK call: organization, project, queueId.

Target delta: show.go ≤ ~130 LOC, show.tpl ≤ ~50 LOC, shared/resolve.go ≤ ~30 LOC, show_test.go ≤ ~450 LOC (20 tests), parent queue.go +3 LOC, docs/pipelines_queue_show.md regenerated via make docs. No changes to other queue siblings (none yet).

Tooling and Verification Checklist

  • Run gofmt / gofumpt on touched files
  • go test ./internal/cmd/pipelines/queue/...
  • go test ./...
  • make lint
  • make docs

Reference Existing Patterns

  • internal/cmd/pr/view/view.goprimary template-engine reference (Primary: view.go:483-549; viewOptions struct at view.go:23-31; template embed at view.go:33-34).
  • internal/cmd/pr/view/view.tplprimary template-file reference (45 lines; same field-bullet style).
  • internal/cmd/boards/workitem/show/show.go (sibling under feat: Implement azdo boards work-item show command #238) — copy structure for the show flow, raw-SDK JSON output, progress lifecycle.
  • internal/cmd/pipelines/pool/show/show.go (sibling under the pool show sub-issue filed in the same wave) — closest sibling; mirror the entire file structure. The only differences are: (a) Use: "show [ORGANIZATION/]PROJECT/QUEUE" vs Use: "show [ORGANIZATION/]POOL"; (b) util.ParseProjectTargetWithDefaultOrganization vs util.ParseTargetWithDefaultOrganization; (c) GetAgentQueue vs GetAgentPool.
  • internal/cmd/boards/workitem/list/list_test.go:765-844setupFakeDeps / stub* fixture; copy structure.
  • internal/cmd/pipelines/variablegroup/delete/delete.goprimary target-resolution precedent (Decision 2 / 2.5): uses Use: "delete [ORGANIZATION/]PROJECT/GROUP", util.ExactArgs(1, "..."), util.ParseProjectTargetWithDefaultOrganization, and a shared.ResolveVariableGroup helper.
  • internal/cmd/util/scope.go:183util.ParseProjectTargetWithDefaultOrganization (project-scoped parser).
  • internal/mocks/taskagent_client_mock.go:456-471 — mock for GetAgentQueue (already generated, do not regenerate).
  • internal/azdo/factory.go:133-139ClientFactory().TaskAgent(...) accessor (reuse).
  • internal/template/template.go — template engine + funcs (reuse, do not reimplement).

References

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions