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
18 changes: 18 additions & 0 deletions Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,25 @@ $ lcls -f CRY-1
----
$ lc issue take CRY-1234
$ lc issue take CRY-456 CRY-789
$ lc issue take CRY-1234 --status "In Progress" <1>
$ lc issue take CRY-1234 -s Todo <2>
----
<1> Self-assign and move to the named workflow state in one update
<2> Short form of `--status`

==== Assign an issue to a team member

[source,sh]
----
$ lc issue assign CRY-1234 --assignee alice <1>
$ lc issue assign CRY-1234 -a alice --status "In Progress" <2>
$ lc issue assign CRY-1234 -a alice -s Todo <3>
$ lc issue assign CRY-1234 <4>
----
<1> Assign to a team member by name (case-insensitive, prefix match)
<2> Assign and move to the named workflow state in one update
<3> Short form of `--status`
<4> Prompts for the assignee interactively

==== Create an issue

Expand Down
14 changes: 13 additions & 1 deletion app/lib/linear_cli/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,11 @@ defmodule LinearCli.CLI do
short: "-a",
long: "--assignee",
help: "Team member name to assign to (prompts if omitted)"
],
status: [
short: "-s",
long: "--status",
help: "Workflow state name to set after assigning (e.g. \"In Progress\")"
]
]
],
Expand Down Expand Up @@ -641,7 +646,14 @@ defmodule LinearCli.CLI do
take: [
name: "take",
about: "Assign one or more issues to yourself",
allow_unknown_args: true
allow_unknown_args: true,
options: [
status: [
short: "-s",
long: "--status",
help: "Workflow state name to set after self-assigning (e.g. \"In Progress\")"
]
]
],
update: [
name: "update",
Expand Down
29 changes: 26 additions & 3 deletions app/lib/linear_cli/cli/commands.ex
Original file line number Diff line number Diff line change
Expand Up @@ -408,12 +408,17 @@ defmodule LinearCli.CLI.Commands do
def issue_take(result, opts \\ [])

def issue_take(%{unknown: issue_ids, options: options}, opts) do
opts = maybe_put_status(opts, Map.get(options, :status))

with {:ok, updates} <- take_issues(issue_ids, opts) do
Display.show(updates, %{output: options.output})
:ok
end
end

defp maybe_put_status(opts, nil), do: opts
defp maybe_put_status(opts, status), do: Keyword.put(opts, :status, status)

defp take_issues(issue_ids, opts) do
issue_ids
|> Enum.reduce_while({:ok, []}, fn issue_id, {:ok, acc} ->
Expand Down Expand Up @@ -545,6 +550,15 @@ defmodule LinearCli.CLI.Commands do
end
end

defp resolve_optional_status(_issue, nil), do: {:ok, nil}

defp resolve_optional_status(issue, name) do
with {:ok, states} <- Linear.workflow_states_by_team(issue.team.id),
{:ok, state} <- resolve_target_state(states, name) do
{:ok, state.id}
end
end

@doc """
Assigns an issue to a team member.

Expand All @@ -560,11 +574,20 @@ defmodule LinearCli.CLI.Commands do
{:ok, members} <- Linear.team_members(issue.team.id),
:ok <- guard_has_members(members, issue),
{:ok, target_member} <- resolve_target_member(members, options.assignee),
{:ok, updated} <- Linear.assign_issue(issue, target_member.id) do
{:ok, state_id} <- resolve_optional_status(issue, Map.get(options, :status)),
{:ok, updated} <- Linear.assign_issue(issue, target_member.id, %{state_id: state_id}) do
Display.show(updated, %{output: options.output})

if options.output != "json",
do: Prompt.ok("#{updated.identifier} assigned to #{target_member.name}")
if options.output != "json" do
msg = "#{updated.identifier} assigned to #{target_member.name}"

msg =
if updated.state,
do: "#{msg} and set to #{updated.state.name}",
else: msg

Prompt.ok(msg)
end

:ok
end
Expand Down
58 changes: 53 additions & 5 deletions app/lib/linear_cli/cli/issue_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -458,21 +458,69 @@ defmodule LinearCli.CLI.IssueHelpers do
@spec gimme_da_issue!(String.t(), keyword()) :: {:ok, %Linear.Issue{}} | {:error, term()}
def gimme_da_issue!(issue_id, opts \\ []) do
issue_id = expand_issue_id(issue_id)
status_opt = parse_status_opt(opts)

with {:ok, me} <- resolve_me(opts),
{:ok, [issue]} <- Linear.issues(%{ids: [issue_id]}) do
assign_or_confirm(issue, me, issue_id)
{:ok, [issue]} <- Linear.issues(%{ids: [issue_id]}),
{:ok, state_id} <- resolve_status_for_issue(issue, status_opt) do
assign_or_confirm(issue, me, issue_id, state_id)
end
end

defp assign_or_confirm(%{assignee: %{id: id}} = issue, %{id: id}, issue_id) do
defp parse_status_opt(opts) do
case Keyword.fetch(opts, :state_id) do
{:ok, id} -> {:resolved, id}
:error -> {:name, Keyword.get(opts, :status)}
end
end

defp resolve_status_for_issue(_issue, {:resolved, id}), do: {:ok, id}
defp resolve_status_for_issue(_issue, {:name, nil}), do: {:ok, nil}

defp resolve_status_for_issue(issue, {:name, name}) do
with {:ok, states} <- Linear.workflow_states_by_team(issue.team.id) do
case resolve_workflow_state(states, name) do
{:ok, state} -> {:ok, state.id}
error -> error
end
end
end

defp resolve_workflow_state(states, name) do
normalized = String.downcase(name)

states
|> Enum.filter(&(String.downcase(&1.name) == normalized))
|> use_prefix_state_matches_if_empty(states, normalized)
|> resolve_workflow_state_matches(states, name)
end

defp use_prefix_state_matches_if_empty([], states, name) do
Enum.filter(states, &String.starts_with?(String.downcase(&1.name), name))
end

defp use_prefix_state_matches_if_empty(matches, _states, _name), do: matches

defp resolve_workflow_state_matches([state], _states, _name), do: {:ok, state}

defp resolve_workflow_state_matches([], states, name) do
available = Enum.map_join(states, ", ", & &1.name)
smells_bad("Unknown status #{inspect(name)}. Available: #{available}")
end

defp resolve_workflow_state_matches(matches, _states, name) do
ambiguous = Enum.map_join(matches, ", ", & &1.name)
smells_bad("Ambiguous status #{inspect(name)}: matches #{ambiguous}")
end

defp assign_or_confirm(%{assignee: %{id: id}} = issue, %{id: id}, issue_id, nil) do
Prompt.say("You are already assigned #{issue_id}")
{:ok, issue}
end

defp assign_or_confirm(issue, me, issue_id) do
defp assign_or_confirm(issue, me, issue_id, state_id) do
Prompt.say("Assigning issue #{issue_id} to ya")
Linear.assign_issue(issue, me.id)
Linear.assign_issue(issue, me.id, %{state_id: state_id})
end

defp resolve_me(opts) do
Expand Down
11 changes: 8 additions & 3 deletions app/lib/linear_cli/linear/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ defmodule LinearCli.Linear.Issue do
# Ruby: Issue#assign!(user)
update :assign do
argument :assignee_id, :string, allow_nil?: false
argument :state_id, :string, allow_nil?: true
manual LinearCli.Linear.Issue.Update.Assign
end

Expand Down Expand Up @@ -337,9 +338,13 @@ defmodule LinearCli.Linear.Issue.Update.Assign do
alias LinearCli.Linear.Issue

def update(changeset, _opts, _context) do
Issue.Update.run(changeset.data.identifier, %{
"assigneeId" => changeset.arguments.assignee_id
})
args = changeset.arguments
state_id = Map.get(args, :state_id)

input = %{"assigneeId" => args.assignee_id}
input = if state_id, do: Map.put(input, "stateId", state_id), else: input

Issue.Update.run(changeset.data.identifier, input)
end
end

Expand Down
Loading