os-backlog: capture + list skills, card CLI (refs #13)
/os-backlog:capture resolves repo -> board via the pure resolver, runs board-ensure transparently when the board is missing, and creates the card at Backlog only. /os-backlog:list is strictly pull-based — board state is never injected unasked. Both skills state the column-ownership rules (create at Backlog only, move Doing -> Review at most, never pull Next, never move Review -> Done, never self-assign hitl cards) and fail soft when the gem/Planka is unavailable. New Backlog::Cards lib class + card-add / cards / snapshot subcommands; fake-client unit tests, no live API. Manual live verification is still pending: the planka-api gem is not installed in this environment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XknQRvihHDpYE47RTmUR4N
This commit is contained in:
parent
8d4b1c7f46
commit
3bcf3a1b48
|
|
@ -1,7 +1,8 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
#
|
#
|
||||||
# os-backlog CLI: board-ensure, activate, archive, resolve.
|
# os-backlog CLI: board-ensure, activate, archive, resolve, card-add,
|
||||||
|
# cards, snapshot.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# os-backlog board-ensure NAME --project Dev|Clients
|
# os-backlog board-ensure NAME --project Dev|Clients
|
||||||
|
|
@ -9,6 +10,9 @@
|
||||||
# os-backlog archive NAME --yes
|
# os-backlog archive NAME --yes
|
||||||
# os-backlog resolve < input.json
|
# os-backlog resolve < input.json
|
||||||
# input.json: {"repo_path": "...", "config": "..."|null, "boards": ["...", ...]}
|
# input.json: {"repo_path": "...", "config": "..."|null, "boards": ["...", ...]}
|
||||||
|
# os-backlog card-add --board NAME --title "..." [--description "..."]
|
||||||
|
# os-backlog cards --board NAME (human-readable per-list card listing)
|
||||||
|
# os-backlog snapshot --board NAME (full board state as JSON; read-only)
|
||||||
#
|
#
|
||||||
# board-ensure/activate/archive talk to Planka over the network (the
|
# board-ensure/activate/archive talk to Planka over the network (the
|
||||||
# installed planka-api gem, credentials from PLANKA_BASE_URL/USERNAME/
|
# installed planka-api gem, credentials from PLANKA_BASE_URL/USERNAME/
|
||||||
|
|
@ -45,8 +49,21 @@ def parse_project_flag(args)
|
||||||
project || fail_soft("--project requires a value (Dev or Clients)")
|
project || fail_soft("--project requires a value (Dev or Clients)")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_flag(args, flag)
|
||||||
|
idx = args.index(flag)
|
||||||
|
return nil unless idx
|
||||||
|
|
||||||
|
args.delete_at(idx)
|
||||||
|
args.delete_at(idx)
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_flag(args, flag, command)
|
||||||
|
parse_flag(args, flag) || fail_soft("#{command} requires #{flag} <value>")
|
||||||
|
end
|
||||||
|
|
||||||
command, *rest = ARGV
|
command, *rest = ARGV
|
||||||
|
|
||||||
|
begin
|
||||||
case command
|
case command
|
||||||
when "board-ensure"
|
when "board-ensure"
|
||||||
name = rest.shift
|
name = rest.shift
|
||||||
|
|
@ -79,6 +96,28 @@ when "resolve"
|
||||||
boards: input.fetch("boards", [])
|
boards: input.fetch("boards", [])
|
||||||
)
|
)
|
||||||
puts resolver.resolve_string
|
puts resolver.resolve_string
|
||||||
|
when "card-add"
|
||||||
|
board = require_flag(rest, "--board", "card-add")
|
||||||
|
title = require_flag(rest, "--title", "card-add")
|
||||||
|
description = parse_flag(rest, "--description")
|
||||||
|
client = build_client
|
||||||
|
card = Backlog::Cards.new(client: client).add(board_name: board, title: title, description: description)
|
||||||
|
puts "created card ##{card['id']} at Backlog on #{board}: #{card['name']}"
|
||||||
|
when "cards"
|
||||||
|
board = require_flag(rest, "--board", "cards")
|
||||||
|
client = build_client
|
||||||
|
snapshot = Backlog::Cards.new(client: client).snapshot(board_name: board)
|
||||||
|
snapshot["lists"].each do |list|
|
||||||
|
puts "#{list['name']} (#{list['cards'].size})"
|
||||||
|
list["cards"].each do |card|
|
||||||
|
labels = card["labels"].empty? ? "" : " [#{card['labels'].join(', ')}]"
|
||||||
|
puts " - #{card['name']}#{labels}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when "snapshot"
|
||||||
|
board = require_flag(rest, "--board", "snapshot")
|
||||||
|
client = build_client
|
||||||
|
puts JSON.pretty_generate(Backlog::Cards.new(client: client).snapshot(board_name: board))
|
||||||
when nil, "-h", "--help"
|
when nil, "-h", "--help"
|
||||||
puts <<~USAGE
|
puts <<~USAGE
|
||||||
usage: os-backlog <command> [options]
|
usage: os-backlog <command> [options]
|
||||||
|
|
@ -88,8 +127,16 @@ when nil, "-h", "--help"
|
||||||
activate NAME
|
activate NAME
|
||||||
archive NAME --yes
|
archive NAME --yes
|
||||||
resolve (reads {"repo_path","config","boards"} JSON from stdin)
|
resolve (reads {"repo_path","config","boards"} JSON from stdin)
|
||||||
|
card-add --board NAME --title "..." [--description "..."]
|
||||||
|
cards --board NAME
|
||||||
|
snapshot --board NAME
|
||||||
USAGE
|
USAGE
|
||||||
exit(command.nil? ? 1 : 0)
|
exit(command.nil? ? 1 : 0)
|
||||||
else
|
else
|
||||||
fail_soft("unknown command #{command.inspect}")
|
fail_soft("unknown command #{command.inspect}")
|
||||||
end
|
end
|
||||||
|
rescue SystemExit
|
||||||
|
raise
|
||||||
|
rescue StandardError => e
|
||||||
|
fail_soft(e.message)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require_relative "backlog/board_spec"
|
require_relative "backlog/board_spec"
|
||||||
require_relative "backlog/board_ensurer"
|
require_relative "backlog/board_ensurer"
|
||||||
|
require_relative "backlog/cards"
|
||||||
require_relative "backlog/config"
|
require_relative "backlog/config"
|
||||||
require_relative "backlog/resolver"
|
require_relative "backlog/resolver"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
require_relative "board_spec"
|
||||||
|
|
||||||
|
module Backlog
|
||||||
|
# Card-level operations over an authenticated Planka client: create a
|
||||||
|
# card at Backlog (the only column the AI ever creates into), and build
|
||||||
|
# a read-only board snapshot (lists with their cards, labels, card-label
|
||||||
|
# assignments) for the list skill and the triage/audit agents.
|
||||||
|
#
|
||||||
|
# Never moves cards between columns and never deletes anything.
|
||||||
|
class Cards
|
||||||
|
# @param client [Planka::Client] an already-authenticated client
|
||||||
|
def initialize(client:)
|
||||||
|
@client = client
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a card at the board's Backlog list. Raises if the board or
|
||||||
|
# its Backlog list doesn't exist (callers run board-ensure first).
|
||||||
|
#
|
||||||
|
# @return [Hash] the created card
|
||||||
|
def add(board_name:, title:, description: nil)
|
||||||
|
board = find_board!(board_name)
|
||||||
|
backlog = find_list!(board, "Backlog")
|
||||||
|
attrs = { name: title, type: "project", position: next_position(board, backlog) }
|
||||||
|
attrs[:description] = description if description
|
||||||
|
@client.cards.create(backlog["id"], attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read-only snapshot of a whole board: every list in BoardSpec order
|
||||||
|
# (plus any extras), each with its cards and their label names.
|
||||||
|
#
|
||||||
|
# @return [Hash] { "board" => name, "labels" => [names],
|
||||||
|
# "lists" => [{ "name", "cards" => [{card fields + "labels"}] }] }
|
||||||
|
def snapshot(board_name:)
|
||||||
|
board = find_board!(board_name)
|
||||||
|
included = @client.boards.get(board["id"])["included"]
|
||||||
|
labels_by_id = (included["labels"] || []).to_h { |l| [l["id"], l["name"]] }
|
||||||
|
card_labels = (included["cardLabels"] || []).group_by { |cl| cl["cardId"] }
|
||||||
|
cards_by_list = (included["cards"] || []).group_by { |c| c["listId"] }
|
||||||
|
|
||||||
|
lists = (included["lists"] || [])
|
||||||
|
.select { |l| l["name"] } # skip built-in archive/trash lists (name: null)
|
||||||
|
.sort_by { |l| l["position"] || Float::INFINITY }
|
||||||
|
.map do |list|
|
||||||
|
cards = (cards_by_list[list["id"]] || []).map do |card|
|
||||||
|
label_names = (card_labels[card["id"]] || []).filter_map { |cl| labels_by_id[cl["labelId"]] }
|
||||||
|
card.merge("labels" => label_names)
|
||||||
|
end
|
||||||
|
{ "name" => list["name"], "cards" => cards }
|
||||||
|
end
|
||||||
|
|
||||||
|
{ "board" => board["name"], "labels" => labels_by_id.values, "lists" => lists }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_board!(name)
|
||||||
|
@client.projects.list.each do |project|
|
||||||
|
board = @client.boards.list(project["id"]).find { |b| b["name"] == name }
|
||||||
|
return board if board
|
||||||
|
end
|
||||||
|
raise "no board named #{name.inspect} found (run board-ensure first)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_list!(board, list_name)
|
||||||
|
list = @client.lists.list(board["id"]).find { |l| l["name"] == list_name }
|
||||||
|
raise "board #{board['name'].inspect} has no #{list_name.inspect} list (run board-ensure)" unless list
|
||||||
|
|
||||||
|
list
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_position(board, list)
|
||||||
|
included = @client.boards.get(board["id"])["included"]
|
||||||
|
positions = (included["cards"] || [])
|
||||||
|
.select { |c| c["listId"] == list["id"] }
|
||||||
|
.filter_map { |c| c["position"] }
|
||||||
|
(positions.max || 0) + 65_536
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
description: Capture work discovered mid-session as a Backlog card on the current repo's Planka board — resolves the board deterministically from the repo path, creates a missing board transparently via board-ensure, and always lands the card at Backlog. Use unprompted WHEN a concrete follow-up task, bug, or piece of deferred work surfaces mid-session that will not be done in this session. Invoked by `/os-backlog:capture`.
|
||||||
|
---
|
||||||
|
|
||||||
|
Capture a piece of work as a card on the right Planka board, cheaply, without interrupting the current task.
|
||||||
|
|
||||||
|
## Column-ownership rules (non-negotiable)
|
||||||
|
|
||||||
|
- **The AI creates cards at Backlog only.** Never create a card in any other list.
|
||||||
|
- **Never pull cards into or out of Next** — Next is human-curated.
|
||||||
|
- **The AI moves cards Doing → Review at most.** Never move a card past Review; never move Review → Done. Done is a human sign-off.
|
||||||
|
- **Never self-assign or start work on a card labeled `hitl`** — those require the human in the loop.
|
||||||
|
- Priority/autonomy labeling is not capture's job — leave new cards unlabeled; the card-triage agent batches that later.
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
All commands use the plugin CLI at `${CLAUDE_PLUGIN_ROOT}/bin/os-backlog`.
|
||||||
|
|
||||||
|
1. **Resolve the board** for the current repo. Gather the inputs and run the pure resolver (no network):
|
||||||
|
```bash
|
||||||
|
# config: contents of .cc-os/config if present, else null
|
||||||
|
# boards: names from Planka — every project's boards, including archived--* ones
|
||||||
|
echo '{"repo_path": "<repo root>", "config": <config-or-null>, "boards": [...]}' \
|
||||||
|
| ${CLAUDE_PLUGIN_ROOT}/bin/os-backlog resolve
|
||||||
|
```
|
||||||
|
To build the `boards` inventory, list boards via `snapshot`/the Planka client; if Planka is unreachable, stop here and report the error — do not guess.
|
||||||
|
2. **Act on the decision:**
|
||||||
|
- `use <board>` → proceed to step 3.
|
||||||
|
- `activate <archived board>` → run `os-backlog activate <name>` (activation is automatic — an archived board matching this repo is simply renamed back), then proceed.
|
||||||
|
- `stop-and-discuss` → do NOT create anything. Tell the user the repo doesn't map to a board and ask where the card should go.
|
||||||
|
3. **Ensure the board exists** (idempotent — safe to run every time):
|
||||||
|
```bash
|
||||||
|
${CLAUDE_PLUGIN_ROOT}/bin/os-backlog board-ensure <board> --project <Dev|Clients>
|
||||||
|
```
|
||||||
|
Project is `Dev` for repos under `~/dev/`, `Clients` for repos under `~/clients/`.
|
||||||
|
4. **Create the card at Backlog:**
|
||||||
|
```bash
|
||||||
|
${CLAUDE_PLUGIN_ROOT}/bin/os-backlog card-add --board <board> --title "<short imperative title>" [--description "<context: repo, file paths, why>"]
|
||||||
|
```
|
||||||
|
5. Confirm to the user in one line: board, title. Then return to the interrupted task.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Fail soft. If the planka-api gem or the Planka instance is unavailable, the CLI prints one clear error and exits nonzero — report that error to the user verbatim and offer to note the task elsewhere. Never retry destructively, never leave partial state unmentioned.
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
description: Show the Planka backlog cards relevant to the current repo, on demand. Pull-only — ONLY use when the user explicitly asks what's on the board / backlog / what's next; never inject board state into a session unasked. Invoked by `/os-backlog:list`.
|
||||||
|
---
|
||||||
|
|
||||||
|
Show the current repo's board state when — and only when — the user asks for it.
|
||||||
|
|
||||||
|
## Pull-only rule (notification policy v2)
|
||||||
|
|
||||||
|
This skill is strictly pull-based. **Never volunteer board state**: not at session start, not after a capture, not "while you're here". The only trigger is an explicit user request ("what's on the backlog?", "/os-backlog:list", "show the board").
|
||||||
|
|
||||||
|
## Column-ownership rules (also apply here)
|
||||||
|
|
||||||
|
Listing is read-only. While discussing the listed cards:
|
||||||
|
- Never pull cards into or out of Next; never move anything past Review; never move Review → Done.
|
||||||
|
- If the user asks you to start work from the list, cards labeled `hitl` are off-limits for self-assignment — flag them back to the human instead.
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Resolve the board for the current repo the same way capture does (`resolve` — see `/os-backlog:capture`). On `stop-and-discuss`, ask the user which board they mean instead of guessing.
|
||||||
|
2. Show the cards:
|
||||||
|
```bash
|
||||||
|
${CLAUDE_PLUGIN_ROOT}/bin/os-backlog cards --board <board>
|
||||||
|
```
|
||||||
|
For a structured view (filtering, counting), use JSON instead:
|
||||||
|
```bash
|
||||||
|
${CLAUDE_PLUGIN_ROOT}/bin/os-backlog snapshot --board <board>
|
||||||
|
```
|
||||||
|
3. Relay the listing compactly, in board-column order (Backlog, Next, Doing, Waiting, Review, Done). If the user asked a narrower question ("what's in review?"), answer only that.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Fail soft: if the planka-api gem or Planka is unreachable, the CLI prints one clear error and exits nonzero — relay it and stop. Never fabricate board state.
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
require_relative "test_helper"
|
||||||
|
|
||||||
|
class CardsTest < Minitest::Test
|
||||||
|
include BacklogTestHelpers
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@client = FakePlankaClient.new
|
||||||
|
@ensurer = Backlog::BoardEnsurer.new(client: @client, human_user_id: "human-1")
|
||||||
|
@ensurer.ensure("llf-schema", project_name: "Dev")
|
||||||
|
@cards = Backlog::Cards.new(client: @client)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_creates_card_at_backlog_list
|
||||||
|
card = @cards.add(board_name: "llf-schema", title: "Fix the flaky import")
|
||||||
|
|
||||||
|
backlog = @client.lists_store.find { |l| l.name == "Backlog" }
|
||||||
|
assert_equal backlog.id, card["listId"]
|
||||||
|
assert_equal "Fix the flaky import", card["name"]
|
||||||
|
assert_equal "project", card["type"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_positions_new_card_after_existing_backlog_cards
|
||||||
|
first = @cards.add(board_name: "llf-schema", title: "one")
|
||||||
|
second = @cards.add(board_name: "llf-schema", title: "two")
|
||||||
|
|
||||||
|
assert_operator second["position"], :>, first["position"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_with_description
|
||||||
|
card = @cards.add(board_name: "llf-schema", title: "t", description: "details here")
|
||||||
|
|
||||||
|
assert_equal "details here", card["description"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_raises_for_missing_board
|
||||||
|
error = assert_raises(RuntimeError) { @cards.add(board_name: "nope", title: "t") }
|
||||||
|
assert_match(/no board named/, error.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_snapshot_shape_and_list_order
|
||||||
|
@cards.add(board_name: "llf-schema", title: "captured task")
|
||||||
|
|
||||||
|
snapshot = @cards.snapshot(board_name: "llf-schema")
|
||||||
|
|
||||||
|
assert_equal "llf-schema", snapshot["board"]
|
||||||
|
assert_equal Backlog::BoardSpec::LISTS, snapshot["lists"].map { |l| l["name"] }
|
||||||
|
backlog = snapshot["lists"].find { |l| l["name"] == "Backlog" }
|
||||||
|
assert_equal ["captured task"], backlog["cards"].map { |c| c["name"] }
|
||||||
|
assert_equal Backlog::BoardSpec::LABEL_NAMES.sort, snapshot["labels"].sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_snapshot_includes_card_label_names
|
||||||
|
card = @cards.add(board_name: "llf-schema", title: "labeled task")
|
||||||
|
p1 = @client.labels_store.find { |l| l.name == "P1" }
|
||||||
|
@client.attach_label(card["id"], p1.id)
|
||||||
|
|
||||||
|
snapshot = @cards.snapshot(board_name: "llf-schema")
|
||||||
|
|
||||||
|
backlog = snapshot["lists"].find { |l| l["name"] == "Backlog" }
|
||||||
|
assert_equal ["P1"], backlog["cards"].first["labels"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_snapshot_raises_for_missing_board
|
||||||
|
assert_raises(RuntimeError) { @cards.snapshot(board_name: "nope") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -16,10 +16,11 @@ module BacklogTestHelpers
|
||||||
class FakePlankaClient
|
class FakePlankaClient
|
||||||
Project = Struct.new(:id, :name, :type, :ownerProjectManagerId, :managers, keyword_init: true)
|
Project = Struct.new(:id, :name, :type, :ownerProjectManagerId, :managers, keyword_init: true)
|
||||||
Board = Struct.new(:id, :project_id, :name, keyword_init: true)
|
Board = Struct.new(:id, :project_id, :name, keyword_init: true)
|
||||||
List = Struct.new(:id, :board_id, :name, :type, keyword_init: true)
|
List = Struct.new(:id, :board_id, :name, :type, :position, keyword_init: true)
|
||||||
Label = Struct.new(:id, :board_id, :name, :color, keyword_init: true)
|
Label = Struct.new(:id, :board_id, :name, :color, keyword_init: true)
|
||||||
|
Card = Struct.new(:id, :list_id, :board_id, :name, :type, :position, :description, keyword_init: true)
|
||||||
|
|
||||||
attr_reader :projects, :boards, :lists, :labels
|
attr_reader :projects, :boards, :lists, :labels, :cards
|
||||||
|
|
||||||
def initialize(reject_colors: [])
|
def initialize(reject_colors: [])
|
||||||
@next_id = 0
|
@next_id = 0
|
||||||
|
|
@ -27,16 +28,24 @@ module BacklogTestHelpers
|
||||||
@boards_store = []
|
@boards_store = []
|
||||||
@lists_store = []
|
@lists_store = []
|
||||||
@labels_store = []
|
@labels_store = []
|
||||||
|
@cards_store = []
|
||||||
|
@card_labels_store = []
|
||||||
|
|
||||||
@projects = ProjectsResource.new(self)
|
@projects = ProjectsResource.new(self)
|
||||||
@boards = BoardsResource.new(self)
|
@boards = BoardsResource.new(self)
|
||||||
@lists = ListsResource.new(self)
|
@lists = ListsResource.new(self)
|
||||||
@labels = LabelsResource.new(self, reject_colors: reject_colors)
|
@labels = LabelsResource.new(self, reject_colors: reject_colors)
|
||||||
|
@cards = CardsResource.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_id = (@next_id += 1).to_s
|
def next_id = (@next_id += 1).to_s
|
||||||
|
|
||||||
attr_reader :projects_store, :boards_store, :lists_store, :labels_store
|
attr_reader :projects_store, :boards_store, :lists_store, :labels_store,
|
||||||
|
:cards_store, :card_labels_store
|
||||||
|
|
||||||
|
def attach_label(card_id, label_id)
|
||||||
|
@card_labels_store << { "id" => next_id, "cardId" => card_id, "labelId" => label_id }
|
||||||
|
end
|
||||||
|
|
||||||
class ProjectsResource
|
class ProjectsResource
|
||||||
def initialize(client) = @client = client
|
def initialize(client) = @client = client
|
||||||
|
|
@ -88,6 +97,19 @@ module BacklogTestHelpers
|
||||||
stringify(board)
|
stringify(board)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get(id)
|
||||||
|
board = @client.boards_store.find { |b| b.id == id }
|
||||||
|
{
|
||||||
|
"item" => stringify(board),
|
||||||
|
"included" => {
|
||||||
|
"lists" => @client.lists.list(id),
|
||||||
|
"labels" => @client.labels.list(id),
|
||||||
|
"cards" => @client.cards.all_for_board(id),
|
||||||
|
"cardLabels" => @client.card_labels_store.dup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def stringify(b) = { "id" => b.id, "projectId" => b.project_id, "name" => b.name }
|
def stringify(b) = { "id" => b.id, "projectId" => b.project_id, "name" => b.name }
|
||||||
|
|
@ -103,14 +125,45 @@ module BacklogTestHelpers
|
||||||
def create(board_id, attrs)
|
def create(board_id, attrs)
|
||||||
raise ArgumentError, "list create requires type" unless attrs[:type]
|
raise ArgumentError, "list create requires type" unless attrs[:type]
|
||||||
|
|
||||||
list = FakePlankaClient::List.new(id: @client.next_id, board_id: board_id, name: attrs[:name], type: attrs[:type])
|
list = FakePlankaClient::List.new(id: @client.next_id, board_id: board_id, name: attrs[:name],
|
||||||
|
type: attrs[:type], position: attrs[:position])
|
||||||
@client.lists_store << list
|
@client.lists_store << list
|
||||||
stringify(list)
|
stringify(list)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def stringify(l) = { "id" => l.id, "boardId" => l.board_id, "name" => l.name, "type" => l.type }
|
def stringify(l)
|
||||||
|
{ "id" => l.id, "boardId" => l.board_id, "name" => l.name, "type" => l.type, "position" => l.position }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CardsResource
|
||||||
|
def initialize(client) = @client = client
|
||||||
|
|
||||||
|
def create(list_id, attrs)
|
||||||
|
raise ArgumentError, "card create requires type" unless attrs[:type]
|
||||||
|
|
||||||
|
list = @client.lists_store.find { |l| l.id == list_id }
|
||||||
|
card = FakePlankaClient::Card.new(
|
||||||
|
id: @client.next_id, list_id: list_id, board_id: list.board_id,
|
||||||
|
name: attrs[:name], type: attrs[:type], position: attrs[:position],
|
||||||
|
description: attrs[:description]
|
||||||
|
)
|
||||||
|
@client.cards_store << card
|
||||||
|
stringify(card)
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_for_board(board_id)
|
||||||
|
@client.cards_store.select { |c| c.board_id == board_id }.map { |c| stringify(c) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def stringify(c)
|
||||||
|
{ "id" => c.id, "listId" => c.list_id, "boardId" => c.board_id, "name" => c.name,
|
||||||
|
"type" => c.type, "position" => c.position, "description" => c.description }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class LabelsResource
|
class LabelsResource
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue