fix: include labels in task export - #1469
Conversation
The exportTasks() function exported task fields (title, description, status, priority, dates, userId) but completely omitted labels. When users export tasks and re-import them, all label assignments were permanently lost — a silent data integrity issue. The get-tasks.ts controller already fetches labels correctly (via inArray query on labelTable), but the export controller did not follow the same pattern. Fix: Query labels for all exported tasks using the same batch pattern as get-tasks.ts, build a taskId -> labels map, and include the labels array in each exported task object.
📝 WalkthroughWalkthrough
ChangesTask export labels
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoFix task export to include labels and prevent export/import data loss
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1. Labels not round-tripped
|
| userId: task.userId || null, | ||
| labels: taskLabelsMap.get(task.id) || [], | ||
| })), |
There was a problem hiding this comment.
1. Labels not round-tripped 🐞 Bug ≡ Correctness
exportTasks() now exports labels per task, but the /task/import request validator and importTasks() data model don’t accept or persist labels, so export→import will still drop all label assignments. This is a correctness gap because the newly-added exported labels are not used anywhere in the import path.
Agent Prompt
### Issue description
`exportTasks()` now emits a `labels` array for each task, but the import endpoint schema + `ImportTask` type do not include labels, and `importTasks()` never creates label records for imported tasks. As a result, the exported labels cannot survive a round-trip.
### Issue Context
- The export format now includes `labels: [{ name, color }]`.
- Import currently validates and processes only a subset of task fields.
### Fix Focus Areas
- apps/api/src/task/index.ts[401-415]
- apps/api/src/task/controllers/import-tasks.ts[13-21]
- apps/api/src/task/controllers/import-tasks.ts[64-77]
### Suggested fix approach
1. Extend the import request validator (`/task/import/:projectId`) to accept an optional `labels` array on each task, e.g. `labels?: Array<{ name: string; color: string }>`.
2. Update `ImportTask` to include the same optional `labels` field.
3. After creating each task in `importTasks()`, insert the provided labels into `labelTable` with `taskId = createdTask.id` and `workspaceId = project.workspaceId` (use `onConflictDoNothing` on `[labelTable.taskId, labelTable.name]` to match existing uniqueness constraints).
4. Decide behavior for missing/invalid label entries (e.g., skip null/empty names, require color).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| .from(labelTable) | ||
| .where(inArray(labelTable.taskId, taskIds)) | ||
| : []; |
There was a problem hiding this comment.
2. Unbounded in labels query 🐞 Bug ➹ Performance
exportTasks() exports all tasks in a project and then queries labels with inArray(labelTable.taskId, taskIds), which can generate a very large IN list for big projects and degrade performance or fail due to query/parameter limits. getTasks() uses the same pattern but caps page size to 100, so exportTasks() is uniquely exposed to large-ID-list behavior.
Agent Prompt
### Issue description
`exportTasks()` builds `taskIds` for *all* tasks in the project and passes them into `inArray(labelTable.taskId, taskIds)`. For large projects, this creates an oversized IN predicate/parameter list, which can cause slow exports and potential failures.
### Issue Context
`getTasks()` uses the same `inArray` approach, but it paginates and caps page size to 100, limiting the size of the IN list. `exportTasks()` has no such cap.
### Fix Focus Areas
- apps/api/src/task/controllers/export-tasks.ts[43-56]
### Suggested fix approach
Prefer a join-based label query instead of `inArray(taskIds)`:
- Query labels by joining `labelTable` to `taskTable` and filtering on `taskTable.projectId = projectId`.
- This removes the need to materialize/push all `taskIds` into the SQL and scales with the database’s join planner rather than request-size/parameter-count.
Example shape:
- `from(labelTable).innerJoin(taskTable, eq(labelTable.taskId, taskTable.id)).where(eq(taskTable.projectId, projectId))`
(Alternatively: chunk `taskIds` into batches and merge results, but a join is simpler here.)
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/api/src/task/controllers/export-tasks.ts (1)
89-89: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd regression tests for the label export contract.
Cover tasks with multiple labels, tasks without labels, and projects with no tasks. Assert each label's
nameandcolor, and assertlabels: []when no labels exist.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/task/controllers/export-tasks.ts` at line 89, Add regression tests for the task export flow covering multiple labels, tasks without labels, and projects with no tasks. Assert exported labels preserve each label’s name and color, and verify tasks without labels return labels: [] while empty projects produce no task exports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/task/controllers/export-tasks.ts`:
- Line 89: Update the exportTasks/importTasks flow so exported labels are
supported during import: extend importTasks to consume labels shaped as { name,
color }, resolve or create labels using the same identity rules as existing
task-label handling, and associate them with imported tasks. Alternatively,
remove labels from exportTasks until import support is implemented.
---
Nitpick comments:
In `@apps/api/src/task/controllers/export-tasks.ts`:
- Line 89: Add regression tests for the task export flow covering multiple
labels, tasks without labels, and projects with no tasks. Assert exported labels
preserve each label’s name and color, and verify tasks without labels return
labels: [] while empty projects produce no task exports.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b3a9c50b-860b-474c-8442-8bfc38dd0910
📒 Files selected for processing (1)
apps/api/src/task/controllers/export-tasks.ts
| dueDate: task.dueDate ? new Date(task.dueDate).toISOString() : null, | ||
| startDate: task.startDate ? new Date(task.startDate).toISOString() : null, | ||
| userId: task.userId || null, | ||
| labels: taskLabelsMap.get(task.id) || [], |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 8 \
--glob '*.ts' \
--glob '*.tsx' \
--glob '*.json' \
'exportTasks|\blabels\b|import.*task|task.*import' . || trueRepository: usekaneo/kaneo
Length of output: 50371
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== candidate files =="
fd -i 'export-tasks|import' apps/api apps/web 2>/dev/null | sed -n '1,120p'
echo
echo "== focused search export/import task labels in api controllers and web hooks =="
rg -n -C 6 \
--glob '*.ts' \
--glob '*.tsx' \
'exportTasks|export.*task|import.*task|createTask|tasks.*labels|labels.*createTask|labels.*importTask|label.*id|label.*name|labels:|' \
apps/api/src apps/web/src | sed -n '1,260p'Repository: usekaneo/kaneo
Length of output: 20398
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== export controller relevant section =="
sed -n '1,140p' apps/api/src/task/controllers/export-tasks.ts | cat -n
echo
echo "== import controller relevant sections =="
sed -n '1,260p' apps/api/src/task/controllers/import-tasks.ts | cat -n
echo
echo "== task schema files =="
fd -i 'task.*schema|schema.*task|task' apps/api/src/schemas apps/api/src -g '*.ts' | sed -n '1,120p'
echo
echo "== label references in api task schemas/validators/controllers =="
rg -n -C 5 \
--glob '*.ts' \
'labels|label' apps/api/src/task apps/api/src/schemas apps/api/src | sed -n '1,260p'
echo
echo "== frontend fetchers relevant sections =="
sed -n '1,220p' apps/web/src/fetchers/task/export-tasks.ts | cat -n
echo
sed -n '1,240p' apps/web/src/fetchers/task/import-tasks.ts | cat -nRepository: usekaneo/kaneo
Length of output: 26837
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== route usages of exportTasks/importTasks =="
rg -n -C 8 \
--glob '*.ts' \
'exportTasks|importTasks' apps/api/src | sed -n '1,260p'
echo
echo "== frontend task import/export route/usages =="
rg -n -C 6 \
--glob '*.ts' \
--glob '*.tsx' \
'useExportTasks|exportTasks|importTasks|UseExportTasks|UseImportTasks|tasks-import-export' apps/web/src | sed -n '1,260p'
echo
echo "== api schemas.ts label/task schemas =="
sed -n '1,220p' apps/api/src/schemas.ts | cat -n
echo
echo "== labelTable definition =="
rg -n -C 8 'labelTable|labels.*taskId|taskId.*label|createTableWithId|uuid|varchar' apps/api/src/database -g '*.ts' | sed -n '1,260p'Repository: usekaneo/kaneo
Length of output: 37919
Import labels when exporting tasks.
exportTasks now emits labels as { name, color }, but importTasks accepts only basic task fields and does not consume or insert task labels. Add label import handling with compatible identity rules, or keep the exported labels field out until import supports it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/task/controllers/export-tasks.ts` at line 89, Update the
exportTasks/importTasks flow so exported labels are supported during import:
extend importTasks to consume labels shaped as { name, color }, resolve or
create labels using the same identity rules as existing task-label handling, and
associate them with imported tasks. Alternatively, remove labels from
exportTasks until import support is implemented.
Problem
The
exportTasks()function exports task fields (title, description, status, priority, dates, userId) but completely omits labels. When users export tasks and re-import them, all label assignments are permanently lost - a silent data integrity issue.The
get-tasks.tscontroller already fetches labels correctly (viainArrayquery onlabelTable), but the export controller did not follow the same pattern.Impact
Any export/import round-trip permanently destroys all label data. Users with carefully organized labeling systems would lose all their categorization with no way to recover it.
Fix
Query labels for all exported tasks using the same batch pattern as
get-tasks.ts:inArray(labelTable.taskId, taskIds)taskId -> labelsmaplabelsarray in each exported task objectChanges
apps/api/src/task/controllers/export-tasks.ts:labelTableimport andinArrayfrom drizzle-ormlabelsfield to each exported taskTesting
labels: [{name, color}]for each task[]Summary by CodeRabbit