fix(kanban): validator falls back to global columns when project has no specifics

PUT /api/tasks/<id>/status returns 400 "Invalid status" whenever the
task belongs to a project that has no project-specific kanban_columns
rows AND the user drops it into a configured global column other than
the four hardcoded fallback keys.

Reproduction:

  1. Project has no project-specific kanban_columns rows.
  2. The instance has 5 globals (project_id IS NULL): todo, in_progress,
     review, done, on_hold.
  3. The kanban UI renders the 5 globals as drop targets for that
     project's tasks.
  4. User drops a task into "On Hold". Frontend sends status="on_hold".
  5. app/routes/tasks.py:1519 calls
       KanbanColumn.get_valid_status_keys(project_id=task.project_id)
     with the project's id.
  6. get_active_columns(project_id=<id>) filters strictly on project_id
     and returns [].
  7. get_valid_status_keys then falls back to the hardcoded list
       ["todo", "in_progress", "review", "done", "cancelled"]
     which is missing "on_hold" (and includes "cancelled", which isn't
     even a configured column).
  8. "on_hold" is not in that list -> 400.

Drops to the four hardcoded keys all returned 200; only "On Hold"
failed, exactly matching the live 200/400 alternation observed in
production logs.

Fix: when there are no project-specific columns, fall back to the
configured global columns from the database (which is the set the UI
is already rendering). The hardcoded list is only used as a last-ditch
fallback when even the globals table is empty - this preserves the
table-not-yet-seeded safety net during fresh migrations.

Pure validator change; no schema change, no behavioural change beyond
accepting the statuses the UI is already offering.
This commit is contained in:
MacJediWizard
2026-05-01 15:02:50 -04:00
parent 9773d57725
commit 945340f609
+14 -2
View File
@@ -108,10 +108,22 @@ class KanbanColumn(db.Model):
@classmethod
def get_valid_status_keys(cls, project_id=None):
"""Get list of all valid status keys (for validation). If project_id is None, returns global column keys."""
"""Get list of all valid status keys (for validation).
If project_id is None, returns global column keys.
If project_id is set but the project has no project-specific
columns, fall back to the configured global columns. The kanban
UI renders global columns in that case, so the validator must
accept the same set — otherwise drops to globally-defined columns
like "on_hold" come back as 400 "Invalid status".
"""
columns = cls.get_active_columns(project_id=project_id)
if not columns and project_id is not None:
columns = cls.get_active_columns(project_id=None)
if not columns:
# Fallback to default statuses if table doesn't exist
# Last-ditch fallback if even global columns are missing
# (e.g. table not yet seeded during a fresh migration).
return ["todo", "in_progress", "review", "done", "cancelled"]
return [col.key for col in columns]