diff --git a/Readme.adoc b/Readme.adoc index 517ad81..08ebed8 100644 --- a/Readme.adoc +++ b/Readme.adoc @@ -160,6 +160,7 @@ so you don't have to type the full name every time: |`issue assign` |`a` |`issue move` |`m`, `mv` |`issue update` |`u` +|`issue view` |`v` |`issue comment` | |`issue pr` |`pull-request` |`team list` |`l`, `ls` @@ -214,6 +215,19 @@ $ lcls --state started --status "Human Review,Gate Approved" <4> <3> Filter by a friendly workflow status name (case-insensitive) <4> Combine state and status filters; both must match +==== View an Issue + +Show full details for a single issue (title, description, and all comments): + +[source,sh] +---- +$ lc issue view CRY-1234 +$ lc i v CRY-1234 <1> +$ lc issue view CRY-1234 --output json <2> +---- +<1> Short alias: `i` for `issue`, `v` for `view` +<2> Output as JSON + ==== Assign one or more issues to yourself (take em!) [source,sh] diff --git a/app/lib/linear_cli/cli.ex b/app/lib/linear_cli/cli.ex index 756683b..1b464e3 100644 --- a/app/lib/linear_cli/cli.ex +++ b/app/lib/linear_cli/cli.ex @@ -109,6 +109,7 @@ defmodule LinearCli.CLI do "st" => "status", "stat" => "status", "u" => "update", + "v" => "view", "pull-request" => "pr" }, "team" => %{"l" => "list", "ls" => "list"}, @@ -204,6 +205,7 @@ defmodule LinearCli.CLI do do: run(&Commands.profile_clear/1, result, halt) defp dispatch([:issue, :list], result, halt), do: run(&Commands.issue_list/1, result, halt) + defp dispatch([:issue, :view], result, halt), do: run(&Commands.issue_view/1, result, halt) defp dispatch([:issue, :assign], result, halt), do: run(&Commands.issue_assign/1, result, halt) defp dispatch([:issue, :create], result, halt), do: run(&Commands.issue_create/1, result, halt) @@ -684,6 +686,13 @@ defmodule LinearCli.CLI do ] ] ], + view: [ + name: "view", + about: "Show full details for a single issue", + args: [ + issue_id: [value_name: "ISSUE_ID", help: "The Issue (i.e. CRY-1)", required: true] + ] + ], assign: [ name: "assign", about: "Assign an issue to a team member", diff --git a/app/lib/linear_cli/cli/commands.ex b/app/lib/linear_cli/cli/commands.ex index 8bf1917..c16f395 100644 --- a/app/lib/linear_cli/cli/commands.ex +++ b/app/lib/linear_cli/cli/commands.ex @@ -286,6 +286,21 @@ defmodule LinearCli.CLI.Commands do end end + @doc """ + Shows full details for a single issue - equivalent to `issue list --full ISSUE_ID`. + + Mirrors `gh issue view`: a dedicated verb for the single-issue display case, + making it discoverable without knowing about `list`'s `--full` flag. + """ + def issue_view(%{args: %{issue_id: issue_id}, options: options}) do + expanded_id = IssueHelpers.expand_issue_id(issue_id) + + with {:ok, [issue]} <- Linear.issues(%{ids: [expanded_id]}) do + Display.show(issue, %{output: options.output, full: true}) + :ok + end + end + defp resolve_project_id(nil, _team_key), do: {:ok, nil} defp resolve_project_id(search, team_key) when is_binary(team_key) do diff --git a/app/test/linear_cli/cli/issue_commands_test.exs b/app/test/linear_cli/cli/issue_commands_test.exs index ff0f087..40d2b10 100644 --- a/app/test/linear_cli/cli/issue_commands_test.exs +++ b/app/test/linear_cli/cli/issue_commands_test.exs @@ -512,6 +512,66 @@ defmodule LinearCli.CLI.IssueCommandsTest do end end + describe "issue view" do + test "prints full issue details (header, description, state)" do + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + %{"query" => _} = Jason.decode!(body) + + Req.Test.json(conn, %{ + "data" => %{ + "issue" => + issue_map(%{ + "state" => %{"id" => "s1", "name" => "In Progress", "type" => "started"} + }) + } + }) + end) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["issue", "view", "CRY-1"]) + end) + + assert output =~ "CRY-1" + assert output =~ "Fix the thing" + assert output =~ "[In Progress]" + end + + test "outputs JSON when --output json is given" do + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + %{"query" => _} = Jason.decode!(body) + Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}}) + end) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["issue", "view", "CRY-1", "--output", "json"]) + end) + + decoded = Jason.decode!(output) + assert decoded["identifier"] == "CRY-1" + assert decoded["title"] == "Fix the thing" + end + + test "lc i v ISSUE_ID alias routes to issue view" do + Req.Test.stub(LinearCli.Api, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + %{"query" => _} = Jason.decode!(body) + Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}}) + end) + + output = + capture_io(fn -> + assert :ok = LinearCli.CLI.main(["i", "v", "CRY-1"]) + end) + + assert output =~ "CRY-1" + assert output =~ "Fix the thing" + end + end + describe "issue create (Ruby: commands/issue/create.rb)" do test "resolves every field, declines to take it, and displays the created issue" do stub_responses([