Add migration to fix subtasκ ordering (#554)
* Add migration to fix subtasκ ordering * Fix test issues
This commit is contained in:
parent
1008130e2c
commit
61ef6d7ac0
4 changed files with 123 additions and 23 deletions
68
backend/migrations/20251117013905-add-order-to-tasks.js
Normal file
68
backend/migrations/20251117013905-add-order-to-tasks.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
safeAddColumns,
|
||||
safeAddIndex,
|
||||
safeRemoveColumn,
|
||||
} = require('../utils/migration-utils');
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// Add order column to tasks table for subtask ordering
|
||||
await safeAddColumns(queryInterface, 'tasks', [
|
||||
{
|
||||
name: 'order',
|
||||
definition: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
comment: 'Order position for subtasks within a parent task',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Add index on parent_task_id and order for efficient subtask queries
|
||||
await safeAddIndex(
|
||||
queryInterface,
|
||||
'tasks',
|
||||
['parent_task_id', 'order'],
|
||||
{
|
||||
name: 'tasks_parent_task_id_order',
|
||||
}
|
||||
);
|
||||
|
||||
// Populate order field for existing subtasks based on created_at
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE tasks
|
||||
SET "order" = subquery.row_num
|
||||
FROM (
|
||||
SELECT id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY parent_task_id
|
||||
ORDER BY created_at ASC
|
||||
) as row_num
|
||||
FROM tasks
|
||||
WHERE parent_task_id IS NOT NULL
|
||||
) AS subquery
|
||||
WHERE tasks.id = subquery.id
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
// Remove the index
|
||||
const indexes = await queryInterface.showIndex('tasks');
|
||||
const indexExists = indexes.some(
|
||||
(index) => index.name === 'tasks_parent_task_id_order'
|
||||
);
|
||||
|
||||
if (indexExists) {
|
||||
await queryInterface.removeIndex(
|
||||
'tasks',
|
||||
'tasks_parent_task_id_order'
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the order column using safe utility
|
||||
await safeRemoveColumn(queryInterface, 'tasks', 'order');
|
||||
},
|
||||
};
|
||||
|
|
@ -139,6 +139,11 @@ module.exports = (sequelize) => {
|
|||
key: 'id',
|
||||
},
|
||||
},
|
||||
order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Order position for subtasks within a parent task',
|
||||
},
|
||||
completed_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
|
|
@ -162,6 +167,10 @@ module.exports = (sequelize) => {
|
|||
{
|
||||
fields: ['parent_task_id'],
|
||||
},
|
||||
{
|
||||
name: 'tasks_parent_task_id_order',
|
||||
fields: ['parent_task_id', 'order'],
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ async function getSubtasks(parentTaskId, userId, timezone) {
|
|||
required: false,
|
||||
},
|
||||
],
|
||||
order: [['created_at', 'ASC']],
|
||||
order: [
|
||||
['order', 'ASC'],
|
||||
['created_at', 'ASC'],
|
||||
], // Order by order field, fallback to created_at
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -49,9 +52,16 @@ async function getSubtasks(parentTaskId, userId, timezone) {
|
|||
async function createSubtasks(parentTaskId, subtasks, userId) {
|
||||
if (!subtasks || !Array.isArray(subtasks)) return;
|
||||
|
||||
// Get the highest order value for existing subtasks
|
||||
const existingSubtasks = await taskRepository.findAll(
|
||||
{ parent_task_id: parentTaskId },
|
||||
{ attributes: ['order'], order: [['order', 'DESC']], limit: 1 }
|
||||
);
|
||||
const maxOrder = existingSubtasks[0]?.order ?? 0;
|
||||
|
||||
const subtasksData = subtasks
|
||||
.filter((subtask) => subtask.name && subtask.name.trim())
|
||||
.map((subtask) => ({
|
||||
.map((subtask, index) => ({
|
||||
name: subtask.name.trim(),
|
||||
parent_task_id: parentTaskId,
|
||||
user_id: userId,
|
||||
|
|
@ -66,6 +76,7 @@ async function createSubtasks(parentTaskId, subtasks, userId) {
|
|||
today: subtask.today || false,
|
||||
recurrence_type: 'none',
|
||||
completion_based: false,
|
||||
order: maxOrder + index + 1, // Assign sequential order values
|
||||
}));
|
||||
|
||||
await taskRepository.createMany(subtasksData);
|
||||
|
|
@ -90,36 +101,43 @@ async function updateSubtasks(taskId, subtasks, userId) {
|
|||
});
|
||||
}
|
||||
|
||||
// Update order for all subtasks to reflect their position in the array
|
||||
const allSubtasksToUpdate = subtasks.filter((s) => s.id);
|
||||
|
||||
const subtasksToUpdate = subtasks.filter(
|
||||
(s) =>
|
||||
s.id &&
|
||||
((s.isEdited && s.name && s.name.trim()) || s._statusChanged)
|
||||
);
|
||||
|
||||
if (subtasksToUpdate.length > 0) {
|
||||
const updatePromises = subtasksToUpdate.map((subtask) => {
|
||||
const updateData = {};
|
||||
if (subtasksToUpdate.length > 0 || allSubtasksToUpdate.length > 0) {
|
||||
const updatePromises = allSubtasksToUpdate.map((subtask, index) => {
|
||||
const updateData = {
|
||||
order: index + 1, // Update order based on position in array
|
||||
};
|
||||
|
||||
if (subtask.isEdited && subtask.name && subtask.name.trim()) {
|
||||
updateData.name = subtask.name.trim();
|
||||
}
|
||||
|
||||
if (subtask._statusChanged || subtask.status !== undefined) {
|
||||
updateData.status = parseStatus(subtask.status);
|
||||
|
||||
if (
|
||||
updateData.status === Task.STATUS.DONE &&
|
||||
!subtask.completed_at
|
||||
) {
|
||||
updateData.completed_at = new Date();
|
||||
} else if (updateData.status !== Task.STATUS.DONE) {
|
||||
updateData.completed_at = null;
|
||||
if (subtasksToUpdate.includes(subtask)) {
|
||||
if (subtask.isEdited && subtask.name && subtask.name.trim()) {
|
||||
updateData.name = subtask.name.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (subtask.priority !== undefined) {
|
||||
updateData.priority =
|
||||
parsePriority(subtask.priority) || Task.PRIORITY.LOW;
|
||||
if (subtask._statusChanged || subtask.status !== undefined) {
|
||||
updateData.status = parseStatus(subtask.status);
|
||||
|
||||
if (
|
||||
updateData.status === Task.STATUS.DONE &&
|
||||
!subtask.completed_at
|
||||
) {
|
||||
updateData.completed_at = new Date();
|
||||
} else if (updateData.status !== Task.STATUS.DONE) {
|
||||
updateData.completed_at = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (subtask.priority !== undefined) {
|
||||
updateData.priority =
|
||||
parsePriority(subtask.priority) || Task.PRIORITY.LOW;
|
||||
}
|
||||
}
|
||||
|
||||
return taskRepository.bulkUpdate(updateData, {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ const TASK_INCLUDES_WITH_SUBTASKS = [
|
|||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
separate: true, // Required for order to work with associations
|
||||
order: [
|
||||
['order', 'ASC'],
|
||||
['created_at', 'ASC'],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue