Skip to content

fix(migrations): add missing 0127_workitemfield_external_ids + idempotent custom-field token API#20

Merged
JOBYINC merged 2 commits into
previewfrom
feat/customfield-api-idempotency-v2
May 21, 2026
Merged

fix(migrations): add missing 0127_workitemfield_external_ids + idempotent custom-field token API#20
JOBYINC merged 2 commits into
previewfrom
feat/customfield-api-idempotency-v2

Conversation

@JOBYINC
Copy link
Copy Markdown
Owner

@JOBYINC JOBYINC commented May 21, 2026

Why (urgency)

origin/preview is currently in a broken migration state:

  • migration 0128_apiactivitylog_on_behalf_of.py already declares dependencies = [('db', '0127_workitemfield_external_ids')]
  • but 0127_workitemfield_external_ids.py itself was never merged to preview
  • result: any fresh python manage.py migrate from preview fails on missing migration

Production isn't seeing this because the lark-stable image lane is built from feature/lark-oauth-provider, which carries 0127. This PR restores parity so preview actually applies.

What

Cherry-picks commit f29db3d9f8 from the (never-pushed-as-its-own-PR) local branch feature/customfield-api-idempotency. Backend-only change, no frontend touched.

  1. Migration 0127_workitemfield_external_ids — adds external_source and external_id nullable CharFields + composite unique constraint on WorkItemField (mirrors the same pattern Issue already uses for external import idempotency).
  2. SerializersWorkItemFieldSerializer / WorkItemFieldOptionSerializer expose external_source and external_id as writable fields.
  3. ViewsWorkItemFieldListAPIEndpoint and WorkItemFieldOptionListAPIEndpoint:
    • GET ?external_source=&external_id= returns the single existing record (mirrors Issue list endpoint).
    • POST returns HTTP 409 + {"error": "...", "id": "<existing-uuid>"} when both external keys already exist for the same project+field scope. Lets idempotent re-imports converge without surprise duplicates.

Files

 apps/api/plane/api/serializers/work_item_field.py  |  4 ++
 apps/api/plane/api/views/work_item_field.py        | 79 ++++++++++++++++++++++
 apps/api/plane/db/migrations/0127_workitemfield_external_ids.py | 37 +++++++
 apps/api/plane/db/models/work_item_field.py        | 10 +++
 4 files changed, 130 insertions(+)

Test plan

  • CI green
  • python manage.py migrate from a clean preview-based DB now applies 0127 → 0128 in sequence (the bug)
  • Migration 0127 dependency points at 0126_project_personal (it does — verified in file header)
  • Reviewed: per original commit message, 15/15 acceptance probes already passed on the local stack (LD_Offset number create/list/set-value/read + idempotent 409+id; Tier single_select + PS/S/A/B options idempotent)
  • No frontend changes; no risk to existing custom-field UI

Note on the automation-RenameIndex drift

makemigrations on this codebase also flags two pre-existing RenameIndex drifts on automationrule/automationrulerun tables. Those are unrelated upstream drifts and are deliberately excluded from 0127 — scope of this migration is strictly the WorkItemField external-id keys. See note in the migration file.

…ken API

The public token custom-field API (/api/v1/.../projects/<id>/fields/)
already existed but lacked the external-id idempotency convention every
other tick_client write uses (Issue: GET-then-create / 409+id). External
agents' ensure_* therefore couldn't safely create field/option schema.

Mirror the Issue pattern exactly:
- WorkItemField + WorkItemFieldOption: add external_source/external_id
  CharField(255, null, blank) (identical decl to Issue/IssueType).
  Migration 0127, additive + nullable, no data migration. (Value layer
  unchanged: PUT upsert is already idempotent via the (issue,field)
  unique key.)
- Token serializers expose external_source/external_id (writable).
- WorkItemFieldListAPIEndpoint / WorkItemFieldOptionListAPIEndpoint:
  GET ?external_source=&external_id= single lookup (mirrors Issue list);
  POST returns 409 + {error, id:<existing>} when the external keys
  already exist (mirrors Issue create), scoped to project / field.

Pre-existing automation RenameIndex drift detected by makemigrations is
deliberately excluded from 0127 (not ours; documented in the migration).

Verified end-to-end on the local stack: 15/15 acceptance probes pass
(LD_Offset number create/list/set-value/read + idempotent 409+id; Tier
single_select + PS/S/A/B options idempotent). No new migration on the
prod line beyond 0127; frontend untouched.
ruff check enforces 120-char max on apps/api; the inline 123-char error
string in WorkItemFieldOptionListAPIEndpoint POST tripped Lint API CI.
Wrap into a multi-line string literal — no behavior change.
@JOBYINC JOBYINC merged commit 4202d4a into preview May 21, 2026
9 checks passed
@JOBYINC JOBYINC deleted the feat/customfield-api-idempotency-v2 branch May 21, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant