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
5 changes: 5 additions & 0 deletions app/lib/linear_cli/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,11 @@ defmodule LinearCli.CLI do
long: "--comment",
help: "Comment to add to the issue. - open an editor"
],
description: [
short: "-d",
long: "--description",
help: "Update the issue description. - to open an editor"
],
project: [
short: "-p",
long: "--project",
Expand Down
1 change: 1 addition & 0 deletions app/lib/linear_cli/cli/commands.ex
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ defmodule LinearCli.CLI.Commands do
Linear.issues(%{ids: Enum.map(issue_ids, &IssueHelpers.expand_issue_id/1)}) do
update_opts = [
comment: options.comment,
description: Map.get(options, :description),
project: options.project,
cancel: flags.cancel,
close: flags.close,
Expand Down
21 changes: 21 additions & 0 deletions app/lib/linear_cli/cli/issue_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,26 @@ defmodule LinearCli.CLI.IssueHelpers do
end
end

@doc """
Updates `issue`'s description to `description_input`, resolving it (asking,
or opening an editor, if not already given - via
`LinearCli.CLI.WhatFor.description_for/1`) first.
"""
@spec update_description(%Linear.Issue{}, String.t() | nil) ::
{:ok, %Linear.Issue{}} | {:error, term()}
def update_description(issue, description_input) do
description = WhatFor.description_for(description_input)

case Linear.update_issue_description(issue, description) do
{:ok, updated} ->
Prompt.ok("#{issue.identifier} description updated")
{:ok, updated}

{:error, reason} ->
{:error, reason}
end
end

@doc """
Dispatches an issue update per whichever of `opts`' keys is set, in Ruby's
exact precedence order:
Expand Down Expand Up @@ -341,6 +361,7 @@ defmodule LinearCli.CLI.IssueHelpers do
opts[:cancel] -> normalize(cancel_issue(issue, opts))
opts[:pr] -> issue_pr(issue, opts)
opts[:project] -> normalize(attach_project(issue, opts[:project]))
opts[:description] -> normalize(update_description(issue, opts[:description]))
opts[:comment] -> :ok
true -> no_action_taken()
end
Expand Down
1 change: 1 addition & 0 deletions app/lib/linear_cli/linear.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ defmodule LinearCli.Linear do
define :attach_issue_to_project, action: :attach_to_project, args: [:project_id]
define :close_issue, action: :close, args: [:state_id]
define :set_issue_status, action: :set_status, args: [:state_id]
define :update_issue_description, action: :update_description, args: [:description]
end

resource LinearCli.Linear.Label do
Expand Down
18 changes: 18 additions & 0 deletions app/lib/linear_cli/linear/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ defmodule LinearCli.Linear.Issue do
argument :state_id, :string, allow_nil?: false
manual LinearCli.Linear.Issue.Update.SetStatus
end

update :update_description do
argument :description, :string, allow_nil?: false
manual LinearCli.Linear.Issue.Update.UpdateDescription
end
end

attributes do
Expand Down Expand Up @@ -378,3 +383,16 @@ defmodule LinearCli.Linear.Issue.Update.SetStatus do
})
end
end

defmodule LinearCli.Linear.Issue.Update.UpdateDescription do
@moduledoc false
use Ash.Resource.ManualUpdate

alias LinearCli.Linear.Issue

def update(changeset, _opts, _context) do
Issue.Update.run(changeset.data.identifier, %{
"description" => changeset.arguments.description
})
end
end
58 changes: 58 additions & 0 deletions app/test/linear_cli/cli/issue_commands_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,64 @@ defmodule LinearCli.CLI.IssueCommandsTest do
assert output =~ "CRY-1 was closed"
end

test "--description updates the issue description via the issueUpdate mutation" do
test_pid = self()

Req.Test.stub(LinearCli.Api, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
decoded = Jason.decode!(body)
query = decoded["query"]

cond do
String.contains?(query, "issue(id: $id)") ->
Req.Test.json(conn, %{"data" => %{"issue" => issue_map()}})

String.contains?(query, "issueUpdate") ->
send(test_pid, {:description, decoded["variables"]["input"]["description"]})
Req.Test.json(conn, issue_updated(%{"description" => "Updated body"}))

true ->
raise "no stub matched query: #{query}"
end
end)

output =
capture_io(fn ->
assert :ok =
LinearCli.CLI.main([
"issue",
"update",
"--description",
"Updated body",
"CRY-1"
])
end)

assert_received {:description, "Updated body"}
assert output =~ "CRY-1 description updated"
end

test "-d short flag also updates the issue description" do
stub_responses([
{"issue(id: $id)", %{"data" => %{"issue" => issue_map()}}},
{"issueUpdate", issue_updated(%{"description" => "Short flag body"})}
])

output =
capture_io(fn ->
assert :ok =
LinearCli.CLI.main([
"issue",
"update",
"-d",
"Short flag body",
"CRY-1"
])
end)

assert output =~ "CRY-1 description updated"
end

test "with no issue ids, exits 22 (Ruby: raise SmellsBad -> exit 22)" do
test_pid = self()
halt = fn code -> send(test_pid, {:halted, code}) end
Expand Down
31 changes: 31 additions & 0 deletions app/test/linear_cli/cli/issue_helpers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,26 @@ defmodule LinearCli.CLI.IssueHelpersTest do
end
end

describe "update_description/2" do
test "resolves and sends the description, printing a confirmation" do
stub_responses([{"issueUpdate", issue_updated(%{"description" => "New body"})}])

assert capture_io(fn ->
assert {:ok, %Issue{description: "New body"}} =
IssueHelpers.update_description(issue(), "New body")
end) =~ "CRY-1 description updated"
end

test "propagates an API error without printing confirmation" do
stub_responses([{"issueUpdate", %{"errors" => [%{"message" => "boom"}]}}])

assert capture_io(fn ->
assert {:error, %Ash.Error.Invalid{}} =
IssueHelpers.update_description(issue(), "New body")
end) == ""
end
end

describe "attach_project/2 (Ruby: CLI::Issue#attach_project)" do
test "resolves the project by name against the team's projects and attaches it" do
stub_responses([
Expand Down Expand Up @@ -363,6 +383,17 @@ defmodule LinearCli.CLI.IssueHelpersTest do
assert output =~ "CRY-1 was attached to Manhattan Rollout"
end

test "with :description, updates the issue description" do
stub_responses([{"issueUpdate", issue_updated(%{"description" => "New body"})}])

output =
capture_io(fn ->
assert :ok = IssueHelpers.update_issue(issue(), description: "New body")
end)

assert output =~ "CRY-1 description updated"
end

test "with only :comment, comments and stops without the 'no action taken' warning" do
stub_responses([{"commentCreate", comment_created()}])

Expand Down
45 changes: 45 additions & 0 deletions app/test/linear_cli/linear/issue_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,49 @@ defmodule LinearCli.Linear.IssueTest do
assert {:error, %Ash.Error.Invalid{}} = Linear.close_issue(issue, "nope")
end
end

describe "update_issue_description/2" do
test "sends description and returns the issue refetched via full_fields" do
issue = struct!(LinearCli.Linear.Issue, id: "i1", identifier: "CRY-1")

Req.Test.stub(LinearCli.Api, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
%{"variables" => %{"id" => id, "input" => input}} = Jason.decode!(body)

assert id == "CRY-1"
assert input == %{"description" => "Updated body"}

Req.Test.json(conn, %{
"data" => %{
"issueUpdate" => %{
"issue" => %{
"id" => "i1",
"identifier" => "CRY-1",
"title" => "Fix it",
"branchName" => "cry-1-fix-it",
"description" => "Updated body",
"assignee" => nil,
"team" => %{"id" => "t1", "key" => "ENG", "name" => "Engineering"},
"comments" => %{"nodes" => []}
}
}
}
})
end)

assert {:ok, updated} = Linear.update_issue_description(issue, "Updated body")
assert updated.description == "Updated body"
end

test "surfaces a GraphQL error" do
issue = struct!(LinearCli.Linear.Issue, id: "i1", identifier: "CRY-1")

Req.Test.stub(LinearCli.Api, fn conn ->
Req.Test.json(conn, %{"errors" => [%{"message" => "unauthorized"}]})
end)

assert {:error, %Ash.Error.Invalid{}} =
Linear.update_issue_description(issue, "Updated body")
end
end
end