Repository navigation
Duplicate a question through the API - #262
Conversation
POST /api/blocks/{block}/duplicate copies a question with its text, type,
settings, answers and Required, but not its rules. The copy goes right
after the original and the form's blocks are renumbered in one
transaction. A group is refused with 422.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PhilReinking
left a comment
There was a problem hiding this comment.
OK at 5a02fb5, CI green.
The new tests fail on main (no route), and the rollback test fails without the transaction. Beyond that: the response equals the block in the block list, nested options, turned-off answers and file limits copy over, and existing submissions stay on the original. A guest gets 401, a block of a trashed form 403. The generated API docs list the endpoint.
The Identifier names the answer in prefill links and webhooks, so a copy that keeps it clashes with the original. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Duplicates of one form at the same time renumbered over each other (16 at once on MariaDB left 21 questions on 15 positions) or deadlocked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test Both blocks had position 0, so MariaDB could list the child first, depending on what ran before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PhilReinking
left a comment
There was a problem hiding this comment.
OK at a26b17b, CI green. Both new tests fail on 5a02fb5. MariaDB, 4 workers, 16 duplicates at once: with the lock 21 questions on 21 positions in 3 of 3 rounds; without it, two on one position in round 1. The child's own position only fixes the test's tie, the import works in any order. A copy without Identifier breaks nothing: new questions start that way, and webhooks fall back to the uuid.
| // duplicates at the same time wait here, else they renumber over each other or deadlock | ||
| $this->form()->lockForUpdate()->first(); |
There was a problem hiding this comment.
Should: SQLite, the Docker image's default, ignores lockForUpdate, and on PHP 8.3 the transaction starts deferred, so a read that turns into a write fails at once. 16 duplicates at once gave 5 to 9 errors 500 "database is locked" per round (positions stayed right). A write first waits on both databases: 16 of 16, 21 on 21, on SQLite and MariaDB. It also marks the form as edited. Tested together with the test change below.
| // duplicates at the same time wait here, else they renumber over each other or deadlock | |
| $this->form()->lockForUpdate()->first(); | |
| // duplicates at the same time wait here, else they renumber over each other or deadlock. | |
| // A write, not lockForUpdate: SQLite ignores that and fails a read that turns into a write | |
| $this->form()->touch(); |
| expect(DB::getQueryLog()[0]['query'])->toStartWith('select * from `forms` where `forms`.`id` = ?')->toEndWith('for update'); | ||
| })->skip(fn () => DB::getDriverName() === 'sqlite', 'SQLite locks the whole database on write'); |
There was a problem hiding this comment.
With the write first, this check runs on SQLite too. Passes on MariaDB and SQLite.
| expect(DB::getQueryLog()[0]['query'])->toStartWith('select * from `forms` where `forms`.`id` = ?')->toEndWith('for update'); | |
| })->skip(fn () => DB::getDriverName() === 'sqlite', 'SQLite locks the whole database on write'); | |
| expect(DB::getQueryLog()[0]['query'])->toMatch('/^update [`"]forms[`"] set /'); | |
| }); |
SQLite ignores lockForUpdate, and PHP 8.3 starts the transaction deferred, so parallel duplicates failed with "database is locked". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the duplicate endpoint (deck9#262) and the editor loading states (deck9#263). The page keeps deck9#263's loading; the document shows its skeleton and error box instead of loading on its own. The old list (Storyboard.vue) stays removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds
POST /api/blocks/{block}/duplicatefor the editor's question menu. It copies a question's text, type, settings, answers and Required, not its rules or Identifier, and puts the copy right after the original.Fixes BITS-275
title): prefill links and webhook names use it, so a shared one clashes.interactionsand emptylogics.Tested: full PHP suite on MariaDB and SQLite. New tests fail on main (no route), the rollback test fails without the transaction. 16 duplicates at once on MariaDB: 21 questions on 15 positions before the lock, 21 on 21 after. Scribe lists the endpoint.
Not tested: no UI uses it yet. The frontend adds the menu entry.
🤖 Generated with Claude Code