diff --git a/skills/linear-cli/references/issue.md b/skills/linear-cli/references/issue.md index 71b624fa..5d70584c 100644 --- a/skills/linear-cli/references/issue.md +++ b/skills/linear-cli/references/issue.md @@ -77,10 +77,11 @@ Options: --all-states - Show issues from all states --assignee - Filter by assignee (username) -A, --all-assignees - Show issues for all assignees - -U, --unassigned - Show only unassigned issues - --sort - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority") - --team - Team to list issues for (if not your default team) - --project - Filter by project name + -U, --unassigned - Show only unassigned issues + --sort - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority") + --team - Team to list issues for (if not your default team) + --all-teams - Show issues from all teams in the workspace + --project - Filter by project name --limit - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: 50) -w, --web - Open in web browser -a, --app - Open in Linear.app diff --git a/src/commands/issue/issue-list.ts b/src/commands/issue/issue-list.ts index 253767d4..b00e3437 100644 --- a/src/commands/issue/issue-list.ts +++ b/src/commands/issue/issue-list.ts @@ -75,6 +75,10 @@ export const listCommand = new Command() "--team ", "Team to list issues for (if not your default team)", ) + .option( + "--all-teams", + "Show issues from all teams in the workspace", + ) .option( "--project ", "Filter by project name", @@ -101,6 +105,7 @@ export const listCommand = new Command() app, allStates, team, + allTeams, project, limit, pager, @@ -131,6 +136,10 @@ export const listCommand = new Command() throw new ValidationError("Cannot use --all-states with --state flag") } + if (allTeams && team) { + throw new ValidationError("Cannot use --all-teams with --team flag") + } + const sort = sortFlag || getOption("issue_sort") as "manual" | "priority" | undefined if (!sort) { @@ -143,11 +152,17 @@ export const listCommand = new Command() `Sort must be one of: ${SortType.values().join(", ")}`, ) } - const teamKey = team || getTeamKey() - if (!teamKey) { - throw new ValidationError( - "Could not determine team key from directory name or team flag", - ) + + let teamKey: string | undefined + if (allTeams) { + teamKey = undefined + } else { + teamKey = team || getTeamKey() + if (!teamKey) { + throw new ValidationError( + "Could not determine team key from directory name or team flag", + ) + } } let projectId: string | undefined diff --git a/src/utils/linear.ts b/src/utils/linear.ts index 87ba178e..61409c68 100644 --- a/src/utils/linear.ts +++ b/src/utils/linear.ts @@ -389,7 +389,7 @@ export async function fetchParentIssueData(parentId: string): Promise< } export async function fetchIssuesForState( - teamKey: string, + teamKey: string | undefined, state: string[] | undefined, assignee?: string, unassigned = false, @@ -410,8 +410,10 @@ export async function fetchIssuesForState( ) } - const filter: IssueFilter = { - team: { key: { eq: teamKey } }, + const filter: IssueFilter = {} + + if (teamKey) { + filter.team = { key: { eq: teamKey } } } if (state) { diff --git a/test/commands/issue/__snapshots__/issue-list.test.ts.snap b/test/commands/issue/__snapshots__/issue-list.test.ts.snap index ac666259..4ed38028 100644 --- a/test/commands/issue/__snapshots__/issue-list.test.ts.snap +++ b/test/commands/issue/__snapshots__/issue-list.test.ts.snap @@ -20,6 +20,7 @@ Options: -U, --unassigned - Show only unassigned issues --sort - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m) --team - Team to list issues for (if not your default team) + --all-teams - Show issues from all teams in the workspace --project - Filter by project name --limit - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: \\x1b[33m50\\x1b[39m) -w, --web - Open in web browser @@ -30,3 +31,14 @@ Options: stderr: "" `; + +snapshot[`Issue List Command - All Teams 1`] = ` +stdout: +"◌ ID TITLE LABELS E STATE UPDATED +⚠⚠⚠ BACKEND-123 Fix authentication bug bug 3 In Progress 1 hour ago +▄▆█ FRONTEND-456 Update user interface feature 5 To Do 1 day ago +--- SEC-789 Security audit review - Backlog 7 days ago +" +stderr: +"" +`; diff --git a/test/commands/issue/issue-list.test.ts b/test/commands/issue/issue-list.test.ts index 277bae42..98937605 100644 --- a/test/commands/issue/issue-list.test.ts +++ b/test/commands/issue/issue-list.test.ts @@ -1,6 +1,7 @@ import { snapshotTest } from "@cliffy/testing" import { listCommand } from "../../../src/commands/issue/issue-list.ts" import { commonDenoArgs } from "../../utils/test-helpers.ts" +import { MockLinearServer } from "../../utils/mock_linear_server.ts" // Test help output await snapshotTest({ @@ -13,3 +14,138 @@ await snapshotTest({ await listCommand.parse() }, }) + +// Test --all-teams flag +await snapshotTest({ + name: "Issue List Command - All Teams", + meta: import.meta, + colors: false, + args: ["--all-teams", "--no-pager", "--sort", "manual"], + denoArgs: commonDenoArgs, + async fn() { + // Use a fixed date for deterministic snapshot tests + const baseDate = new Date("2024-01-15T12:00:00Z") + const server = new MockLinearServer([ + { + queryName: "GetIssuesForState", + response: { + data: { + issues: { + nodes: [ + { + id: "issue-1", + identifier: "BACKEND-123", + title: "Fix authentication bug", + priority: 1, + estimate: 3, + assignee: { + initials: "JD", + }, + state: { + id: "state-1", + name: "In Progress", + color: "#3b82f6", + }, + labels: { + nodes: [ + { + id: "label-1", + name: "bug", + color: "#ef4444", + }, + ], + }, + updatedAt: new Date(baseDate.getTime() - 1000 * 60 * 60) + .toISOString(), // 1 hour ago + }, + { + id: "issue-2", + identifier: "FRONTEND-456", + title: "Update user interface", + priority: 2, + estimate: 5, + assignee: { + initials: "AS", + }, + state: { + id: "state-2", + name: "To Do", + color: "#6b7280", + }, + labels: { + nodes: [ + { + id: "label-2", + name: "feature", + color: "#10b981", + }, + ], + }, + updatedAt: new Date(baseDate.getTime() - 1000 * 60 * 60 * 24) + .toISOString(), // 1 day ago + }, + { + id: "issue-3", + identifier: "SEC-789", + title: "Security audit review", + priority: 0, + estimate: null, + assignee: null, + state: { + id: "state-3", + name: "Backlog", + color: "#94a3b8", + }, + labels: { + nodes: [], + }, + updatedAt: new Date( + baseDate.getTime() - 1000 * 60 * 60 * 24 * 7, + ).toISOString(), // 1 week ago + }, + ], + pageInfo: { + hasNextPage: false, + endCursor: null, + }, + }, + }, + }, + }, + ]) + + try { + await server.start() + Deno.env.set("LINEAR_GRAPHQL_ENDPOINT", server.getEndpoint()) + Deno.env.set("LINEAR_API_KEY", "Bearer test-token") + + // Mock the current date to make time calculations deterministic + const originalDate = globalThis.Date + // @ts-ignore: Mocking Date for testing + globalThis.Date = class extends originalDate { + // deno-lint-ignore constructor-super + constructor(...args: unknown[]) { + if (args.length === 0) { + super(baseDate.getTime()) + } else { + // @ts-ignore: Mocking Date for testing + super(...args) + } + } + + static override now() { + return baseDate.getTime() + } + } + + await listCommand.parse() + + // Restore original Date + globalThis.Date = originalDate + } finally { + await server.stop() + Deno.env.delete("LINEAR_GRAPHQL_ENDPOINT") + Deno.env.delete("LINEAR_API_KEY") + } + }, +})