Add a JSON cards endpoint for workflow columns (#2800)

* Add JSON endpoint for cards in workflow columns

* Add cards URL to column JSON responses

* Add coverage for column cards authorization
This commit is contained in:
Rob Zolkos
2026-04-09 13:17:02 -04:00
committed by GitHub
parent 1c6141c131
commit e422d5b37e
7 changed files with 147 additions and 5 deletions
@@ -0,0 +1,19 @@
class Boards::Columns::CardsController < ApplicationController
include BoardScoped
before_action :set_column
def index
set_page_and_extract_portion_from @column.cards.active.latest.with_golden_first.preloaded
fresh_when etag: @page.records
respond_to do |format|
format.json
end
end
private
def set_column
@column = @board.columns.find(params[:column_id])
end
end
@@ -0,0 +1 @@
json.array! @page.records, partial: "cards/card", as: :card
+1
View File
@@ -1,4 +1,5 @@
json.cache! column do json.cache! column do
json.(column, :id, :name, :color) json.(column, :id, :name, :color)
json.created_at column.created_at.utc json.created_at column.created_at.utc
json.cards_url board_column_cards_url(column.board, column)
end end
+5 -1
View File
@@ -39,7 +39,11 @@ Rails.application.routes.draw do
resource :closed resource :closed
end end
resources :columns resources :columns do
scope module: :columns do
resources :cards, only: :index
end
end
end end
resources :cards, only: :create resources :cards, only: :create
+89 -4
View File
@@ -14,20 +14,22 @@ __Response:__
"id": "03f5v9zkft4hj9qq0lsn9ohcm", "id": "03f5v9zkft4hj9qq0lsn9ohcm",
"name": "Recording", "name": "Recording",
"color": "var(--color-card-default)", "color": "var(--color-card-default)",
"created_at": "2025-12-05T19:36:35.534Z" "created_at": "2025-12-05T19:36:35.534Z",
"cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcm/cards"
}, },
{ {
"id": "03f5v9zkft4hj9qq0lsn9ohcn", "id": "03f5v9zkft4hj9qq0lsn9ohcn",
"name": "Published", "name": "Published",
"color": "var(--color-card-4)", "color": "var(--color-card-4)",
"created_at": "2025-12-05T19:36:35.534Z" "created_at": "2025-12-05T19:36:35.534Z",
"cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcn/cards"
} }
] ]
``` ```
## `GET /:account_slug/boards/:board_id/columns/:column_id` ## `GET /:account_slug/boards/:board_id/columns/:column_id`
Returns the specified column. Returns the specified column metadata.
__Response:__ __Response:__
@@ -36,10 +38,93 @@ __Response:__
"id": "03f5v9zkft4hj9qq0lsn9ohcm", "id": "03f5v9zkft4hj9qq0lsn9ohcm",
"name": "In Progress", "name": "In Progress",
"color": "var(--color-card-default)", "color": "var(--color-card-default)",
"created_at": "2025-12-05T19:36:35.534Z" "created_at": "2025-12-05T19:36:35.534Z",
"cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcm/cards"
} }
``` ```
## `GET /:account_slug/boards/:board_id/columns/:column_id/cards`
Returns a paginated list of open cards in the specified workflow column.
This only includes cards triaged into that column. Cards in "Maybe?", "Not Now", and "Done"
are not included.
The response items have the same shape as `GET /:account_slug/cards`.
__Response:__
```json
[
{
"id": "03f5vaeq985jlvwv3arl4srq2",
"number": 1,
"title": "First!",
"status": "published",
"description": "Hello, World!",
"description_html": "<div class=\"action-text-content\"><p>Hello, World!</p></div>",
"image_url": null,
"has_attachments": false,
"tags": ["programming"],
"closed": false,
"postponed": false,
"golden": false,
"last_active_at": "2025-12-05T19:38:48.553Z",
"created_at": "2025-12-05T19:38:48.540Z",
"url": "http://fizzy.localhost:3006/897362094/cards/4",
"board": {
"id": "03f5v9zkft4hj9qq0lsn9ohcm",
"name": "Fizzy",
"all_access": true,
"created_at": "2025-12-05T19:36:35.534Z",
"auto_postpone_period_in_days": 30,
"url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm",
"creator": {
"id": "03f5v9zjw7pz8717a4no1h8a7",
"name": "David Heinemeier Hansson",
"role": "owner",
"active": true,
"email_address": "david@example.com",
"created_at": "2025-12-05T19:36:35.401Z",
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7",
"avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar"
}
},
"column": {
"id": "03f5v9zkft4hj9qq0lsn9ohcn",
"name": "In Progress",
"color": "var(--color-card-4)",
"created_at": "2025-12-05T19:36:35.534Z",
"cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcn/cards"
},
"creator": {
"id": "03f5v9zjw7pz8717a4no1h8a7",
"name": "David Heinemeier Hansson",
"role": "owner",
"active": true,
"email_address": "david@example.com",
"created_at": "2025-12-05T19:36:35.401Z",
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7",
"avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar"
},
"assignees": [
{
"id": "03f5v9zjw7pz8717a4no1h8a7",
"name": "David Heinemeier Hansson",
"role": "owner",
"active": true,
"email_address": "david@example.com",
"created_at": "2025-12-05T19:36:35.401Z",
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7",
"avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar"
}
],
"has_more_assignees": false,
"comments_url": "http://fizzy.localhost:3006/897362094/cards/4/comments",
"reactions_url": "http://fizzy.localhost:3006/897362094/cards/4/reactions"
}
]
```
## `POST /:account_slug/boards/:board_id/columns` ## `POST /:account_slug/boards/:board_id/columns`
Creates a new column on the board. Creates a new column on the board.
@@ -0,0 +1,29 @@
require "test_helper"
class Boards::Columns::CardsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "index as JSON" do
column = columns(:writebook_in_progress)
get board_column_cards_path(column.board, column), as: :json
assert_response :success
assert_kind_of Array, @response.parsed_body
assert_equal [ cards(:text).number ], @response.parsed_body.pluck("number")
assert_equal "1", response.headers["X-Total-Count"]
end
test "cannot access cards on board without access as JSON" do
board = boards(:private)
column = board.columns.create!(name: "Secret")
logout_and_sign_in_as :jason
get board_column_cards_path(board, column), as: :json
assert_response :not_found
end
end
@@ -55,6 +55,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest
assert_response :success assert_response :success
assert_equal board.columns.count, @response.parsed_body.count assert_equal board.columns.count, @response.parsed_body.count
assert_equal board_column_cards_url(board, board.columns.sorted.first), @response.parsed_body.first["cards_url"]
end end
test "show as JSON" do test "show as JSON" do
@@ -64,6 +65,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest
assert_response :success assert_response :success
assert_equal column.id, @response.parsed_body["id"] assert_equal column.id, @response.parsed_body["id"]
assert_equal board_column_cards_url(column.board, column), @response.parsed_body["cards_url"]
end end
test "create as JSON" do test "create as JSON" do
@@ -76,6 +78,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest
assert_response :created assert_response :created
assert_equal board_column_path(board, Column.last, format: :json), @response.headers["Location"] assert_equal board_column_path(board, Column.last, format: :json), @response.headers["Location"]
assert_equal "New Column", @response.parsed_body["name"] assert_equal "New Column", @response.parsed_body["name"]
assert_equal board_column_cards_url(board, Column.last), @response.parsed_body["cards_url"]
end end
test "update as JSON" do test "update as JSON" do