Skip to content

Commit 6c43e39

Browse files
bougymanclaude
andauthored
feat(issue-helpers): add move_issue/2 for moving an issue to a resolved project (#186)
## Summary - Add `IssueHelpers.move_issue/2` that accepts a pre-resolved `%Project{}` struct and moves an issue to it via `Linear.attach_issue_to_project/2`, printing a confirmation message (`"IDENTIFIER was moved to PROJECT_NAME"`) - Refactor `attach_project/2` to delegate to `move_issue/2` after resolving the search string, removing duplicated attachment logic - Update tests to reflect the new `"was moved to"` confirmation message (previously `"was attached to"`) - Add 2 new unit tests for `move_issue/2` directly (success + error propagation cases) ## Test plan - [x] `mix test test/linear_cli/cli/issue_helpers_test.exs` — all 36 tests pass (was 34) - [x] `mix ci` from repo root — all checks pass (deps audit, format, credo, usage_rules, tests) - [x] Existing `attach_project/2` test still passes (confirmation message updated to match new behavior) - [x] Existing `update_issue/2` dispatch `:project` test still passes Closes https://linear.app/the-rubyists/issue/EXT-8 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b47f01f commit 6c43e39

2 files changed

Lines changed: 50 additions & 12 deletions

File tree

app/lib/linear_cli/cli/issue_helpers.ex

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,28 @@ defmodule LinearCli.CLI.IssueHelpers do
282282
:ok
283283
end
284284

285+
@doc """
286+
Moves `issue` to the already-resolved `project`, calling
287+
`LinearCli.Linear.attach_issue_to_project/2` and printing a confirmation.
288+
289+
Unlike `attach_project/2`, this function takes a pre-resolved
290+
`%LinearCli.Linear.Project{}` struct rather than a search string. Callers
291+
that need to resolve a search string first should use `attach_project/2`,
292+
which delegates here after resolution.
293+
"""
294+
@spec move_issue(%Linear.Issue{}, %Linear.Project{}) ::
295+
{:ok, %Linear.Issue{}} | {:error, term()}
296+
def move_issue(issue, project) do
297+
case Linear.attach_issue_to_project(issue, project.id) do
298+
{:ok, updated} ->
299+
Prompt.ok("#{issue.identifier} was moved to #{project.name}")
300+
{:ok, updated}
301+
302+
{:error, reason} ->
303+
{:error, reason}
304+
end
305+
end
306+
285307
@doc """
286308
Attaches `issue` to a project matched against `project_search` among its
287309
team's projects (`LinearCli.CLI.Projects.project_for/2`, prompting to
@@ -291,22 +313,16 @@ defmodule LinearCli.CLI.IssueHelpers do
291313
`project_search` matching nothing in an empty project list (`project_for`
292314
returning `nil`) - the same faithfully-ported crash risk Ruby's own
293315
`nil.id` would hit.
316+
317+
Resolves the project from the search string, then delegates to `move_issue/2`.
294318
"""
295319
@spec attach_project(%Linear.Issue{}, String.t() | nil) ::
296320
{:ok, %Linear.Issue{}} | {:error, term()}
297321
def attach_project(issue, project_search) do
298322
with {:ok, projects} <-
299323
Linear.projects_by_team(issue.team.id, %{search: project_search}) do
300324
project = Projects.project_for(projects, project_search)
301-
302-
case Linear.attach_issue_to_project(issue, project.id) do
303-
{:ok, updated} ->
304-
Prompt.ok("#{issue.identifier} was attached to #{project.name}")
305-
{:ok, updated}
306-
307-
{:error, reason} ->
308-
{:error, reason}
309-
end
325+
move_issue(issue, project)
310326
end
311327
end
312328

app/test/linear_cli/cli/issue_helpers_test.exs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule LinearCli.CLI.IssueHelpersTest do
33
import ExUnit.CaptureIO
44

55
alias LinearCli.CLI.IssueHelpers
6-
alias LinearCli.Linear.{Comment, Issue, Team, User, WorkflowState}
6+
alias LinearCli.Linear.{Comment, Issue, Project, Team, User, WorkflowState}
77

88
# Every helper under test accepts an already-loaded resource struct (no
99
# data-layer fetch happens inside these functions themselves, mirroring
@@ -283,6 +283,28 @@ defmodule LinearCli.CLI.IssueHelpersTest do
283283
end
284284
end
285285

286+
describe "move_issue/2" do
287+
test "moves the issue to the resolved project and prints a confirmation" do
288+
stub_responses([{"issueUpdate", issue_updated()}])
289+
290+
project = %Project{id: "p1", name: "Manhattan Rollout"}
291+
292+
assert capture_io(fn ->
293+
assert {:ok, %Issue{}} = IssueHelpers.move_issue(issue(), project)
294+
end) =~ "CRY-1 was moved to Manhattan Rollout"
295+
end
296+
297+
test "propagates an API error without printing confirmation" do
298+
stub_responses([{"issueUpdate", %{"errors" => [%{"message" => "boom"}]}}])
299+
300+
project = %Project{id: "p1", name: "Manhattan Rollout"}
301+
302+
assert capture_io(fn ->
303+
assert {:error, %Ash.Error.Invalid{}} = IssueHelpers.move_issue(issue(), project)
304+
end) == ""
305+
end
306+
end
307+
286308
describe "attach_project/2 (Ruby: CLI::Issue#attach_project)" do
287309
test "resolves the project by name against the team's projects and attaches it" do
288310
stub_responses([
@@ -303,7 +325,7 @@ defmodule LinearCli.CLI.IssueHelpersTest do
303325
assert capture_io(fn ->
304326
assert {:ok, %Issue{}} =
305327
IssueHelpers.attach_project(issue(), "Manhattan Rollout")
306-
end) =~ "CRY-1 was attached to Manhattan Rollout"
328+
end) =~ "CRY-1 was moved to Manhattan Rollout"
307329
end
308330
end
309331

@@ -380,7 +402,7 @@ defmodule LinearCli.CLI.IssueHelpersTest do
380402
assert :ok = IssueHelpers.update_issue(issue(), project: "Manhattan Rollout")
381403
end)
382404

383-
assert output =~ "CRY-1 was attached to Manhattan Rollout"
405+
assert output =~ "CRY-1 was moved to Manhattan Rollout"
384406
end
385407

386408
test "with :description, updates the issue description" do

0 commit comments

Comments
 (0)