Repository navigation
feat(task-filters): enable searchable status & phase filters #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
a957c63
174833c
6040f39
572b6e7
7ab59cf
4b5c784
ea9bb60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Forward migration to ensure the recurring tasks unique index uses the | ||
| * immutable UTC date expression on tasks.end_date (which is TIMESTAMP WITH TIME ZONE). | ||
| * | ||
| * tasks.end_date is TIMESTAMPTZ (database/sql/1_tables.sql). In PostgreSQL: | ||
| * - Casting TIMESTAMPTZ directly to DATE (end_date::DATE) depends on the session | ||
| * TimeZone setting and is therefore STABLE, which causes PostgreSQL to reject | ||
| * it in index expressions with "functions in index expression must be marked IMMUTABLE". | ||
| * - Converting with AT TIME ZONE 'UTC' produces a TIMESTAMP (without time zone) | ||
| * at a fixed reference point, making ((end_date AT TIME ZONE 'UTC')::DATE) IMMUTABLE. | ||
| * | ||
| * This migration idempotently ensures idx_tasks_schedule_end_date_unique exists | ||
| * with the correct immutable UTC expression, and restores the prior baseline | ||
| * uniqueness constraint on tasks (schedule_id, end_date) upon rollback. | ||
| */ | ||
|
|
||
| /** @type {import('node-pg-migrate').ColumnDefinitions | undefined} */ | ||
| exports.shorthands = undefined; | ||
|
|
||
| /** @param {import('node-pg-migrate').MigrationBuilder} pgm */ | ||
| exports.up = async (pgm) => { | ||
| pgm.sql(` | ||
| -- ============================================================================ | ||
| -- Fix idx_tasks_schedule_end_date_unique | ||
| -- Recreate the unique index with the immutable UTC expression for TIMESTAMPTZ: | ||
| -- ((end_date AT TIME ZONE 'UTC')::DATE) | ||
| -- ============================================================================ | ||
|
|
||
| DROP INDEX IF EXISTS idx_tasks_schedule_end_date_unique; | ||
|
|
||
| CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique | ||
| ON tasks (schedule_id, ((end_date AT TIME ZONE 'UTC')::DATE)) | ||
| WHERE schedule_id IS NOT NULL AND end_date IS NOT NULL; | ||
|
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Find the type of tasks.end_date
rg -n -C2 'end_date' worklenz-backend/database --glob '*.sql' | rg -i 'timestamp|date' | head -30
# Find usage of the index and its duplicate-handling logic
rg -n 'idx_tasks_schedule_end_date_unique|ON CONFLICT.*schedule_id' worklenz-backendRepository: Worklenz/worklenz Length of output: 4497 🏁 Script executed: set -eu
printf '%s\n' '--- reviewed migration ---'
nl -ba worklenz-backend/database/pg-migrations/1791364628703_fix_recurring_tasks_immutable_index.js | sed -n '1,90p'
printf '%s\n' '--- preceding recurring-task migrations ---'
for f in \
worklenz-backend/database/pg-migrations/1769558401000_fix_recurring_tasks.js \
worklenz-backend/database/pg-migrations/1770681600000_recurring_tasks_complete_fix.js; do
printf '%s\n' "--- $f ---"
nl -ba "$f" | sed -n '1,100p'
done
printf '%s\n' '--- tasks schema and baseline index ---'
nl -ba worklenz-backend/database/sql/1_tables.sql | sed -n '785,815p;2238,2260p'
printf '%s\n' '--- recurring-task references and duplicate checks ---'
rg -n -F --glob '*.{js,ts,sql}' -- 'idx_tasks_schedule_end_date_unique' worklenz-backend || test "$?" -eq 1
rg -n -i --glob '*.{js,ts,sql}' 'schedule_id|recurring.?task|end_date' worklenz-backend/src worklenz-backend/database 2>/dev/null | rg -i 'insert|conflict|duplicate|unique|schedule_id|end_date' | head -160 || trueRepository: Worklenz/worklenz Length of output: 35372 🏁 Script executed: set -eu
printf '%s\n' '--- tasks table declaration ---'
rg -n -F --glob '*.sql' -- 'CREATE TABLE IF NOT EXISTS tasks' worklenz-backend/database
rg -n -F --glob '*.sql' -- 'CREATE TABLE tasks' worklenz-backend/database || true
python3 - <<'PY'
from pathlib import Path
for p in Path('worklenz-backend/database').rglob('*.sql'):
text = p.read_text(errors='replace')
for marker in ('CREATE TABLE IF NOT EXISTS tasks', 'CREATE TABLE tasks'):
pos = text.find(marker)
if pos >= 0:
start = text.rfind('\n', 0, pos) + 1
end = text.find(');', pos)
print(f'--- {p}:{text.count(chr(10), 0, pos)+1} ---')
print(text[start:end+3])
raise SystemExit
PY
printf '%s\n' '--- recurring-task function bodies and task insertion paths ---'
nl -ba worklenz-backend/database/sql/4_functions.sql | sed -n '1020,1145p'
rg -n -i --glob '*.{sql,js,ts}' 'schedule_id.*end_date|end_date.*schedule_id|create_quick_task|create_recurring_task|INSERT INTO tasks' worklenz-backend/database worklenz-backend/src | head -140 || trueRepository: Worklenz/worklenz Length of output: 22388 🏁 Script executed: set -eu
printf '%s\n' '--- recurring task creation and date calculation ---'
nl -ba worklenz-backend/src/cron_jobs/recurring-tasks.ts | sed -n '1,90p;220,350p'
printf '%s\n' '--- migration framework configuration ---'
rg -n -i --glob '*.{json,js,ts,yml,yaml}' 'node-pg-migrate|migration.*transaction|singleTransaction|事务' worklenz-backend package.json 2>/dev/null | head -100 || trueRepository: Worklenz/worklenz Length of output: 25528 🌐 Web query:
💡 Result: Use a valid immutable date key before creating the unique index.
Use a separate The recurring-task job calculates dates in each schedule’s If the earlier index is absent, 🤖 Prompt for AI Agents |
||
| `); | ||
| }; | ||
|
|
||
| /** @param {import('node-pg-migrate').MigrationBuilder} pgm */ | ||
| exports.down = async (pgm) => { | ||
| pgm.sql(` | ||
| -- ============================================================================ | ||
| -- Restore preceding baseline idx_tasks_schedule_end_date_unique | ||
| -- Recreate the prior unique index using the baseline raw timestamp key | ||
| -- from database/sql/1_tables.sql to maintain uniqueness on rollback. | ||
| -- ============================================================================ | ||
|
|
||
| DROP INDEX IF EXISTS idx_tasks_schedule_end_date_unique; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Restore the preceding unique index on rollback
|
||
|
|
||
| CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique | ||
| ON tasks (schedule_id, end_date) | ||
| WHERE schedule_id IS NOT NULL AND end_date IS NOT NULL; | ||
| `); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import path from "path"; | ||
|
|
||
| interface Migration { | ||
| up: (builder: { sql: (statement: string) => void }) => Promise<void>; | ||
| down: (builder: { sql: (statement: string) => void }) => Promise<void>; | ||
| } | ||
|
|
||
| describe("1791364628703_fix_recurring_tasks_immutable_index migration round-trip", () => { | ||
| const migrationPath = path.resolve( | ||
| __dirname, | ||
| "../../database/pg-migrations/1791364628703_fix_recurring_tasks_immutable_index.js" | ||
| ); | ||
| // eslint-disable-next-line @typescript-eslint/no-var-requires | ||
| const migration = require(migrationPath) as Migration; | ||
|
|
||
| it("applies up migration using immutable UTC date expression for timestamptz", async () => { | ||
| const statements: string[] = []; | ||
| await migration.up({ sql: statement => statements.push(statement) }); | ||
| const sql = statements.join("\n"); | ||
|
|
||
| expect(sql).toContain("DROP INDEX IF EXISTS idx_tasks_schedule_end_date_unique;"); | ||
| expect(sql).toContain("CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique"); | ||
| expect(sql).toContain("ON tasks (schedule_id, ((end_date AT TIME ZONE 'UTC')::DATE))"); | ||
| expect(sql).toContain("WHERE schedule_id IS NOT NULL AND end_date IS NOT NULL;"); | ||
| }); | ||
|
|
||
| it("restores baseline unique index on rollback to preserve uniqueness guard", async () => { | ||
| const statements: string[] = []; | ||
| await migration.down({ sql: statement => statements.push(statement) }); | ||
| const sql = statements.join("\n"); | ||
|
|
||
| expect(sql).toContain("DROP INDEX IF EXISTS idx_tasks_schedule_end_date_unique;"); | ||
| expect(sql).toContain("CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique"); | ||
| expect(sql).toContain("ON tasks (schedule_id, end_date)"); | ||
| expect(sql).toContain("WHERE schedule_id IS NOT NULL AND end_date IS NOT NULL;"); | ||
| }); | ||
|
|
||
| it("completes round-trip ensuring uniqueness protection is maintained after down", async () => { | ||
| const upStatements: string[] = []; | ||
| await migration.up({ sql: statement => upStatements.push(statement) }); | ||
|
|
||
| const downStatements: string[] = []; | ||
| await migration.down({ sql: statement => downStatements.push(statement) }); | ||
|
|
||
| const upSql = upStatements.join("\n"); | ||
| const downSql = downStatements.join("\n"); | ||
|
|
||
| // up creates immutable index | ||
| expect(upSql).toMatch(/CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique\s+ON tasks \(schedule_id, \(\(end_date AT TIME ZONE 'UTC'\)::DATE\)\)/); | ||
| // down restores preceding baseline index | ||
| expect(downSql).toMatch(/CREATE UNIQUE INDEX idx_tasks_schedule_end_date_unique\s+ON tasks \(schedule_id, end_date\)/); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Keep an immutable UTC date expression for the timestamptz column. tasks.end_date is TIMESTAMP WITH TIME ZONE (database/sql/1_tables.sql). Casting it directly to DATE depends on the session TimeZone and is STABLE, so CREATE UNIQUE INDEX here fails with "functions in index expression must be marked IMMUTABLE". I reproduced this with a minimal tasks(schedule_id uuid, end_date timestamptz) table: the existing ((end_date AT TIME ZONE 'UTC')::DATE) index succeeds, while this new end_date::DATE index fails. The comment describing the original expression as non-immutable is reversed for this column type. Retain the explicit UTC expression and address transaction wrapping separately; this forward migration currently breaks migrate:up even on a database that already applied the historical migrations successfully.