Issue 266 Dragging A Task Told Nobody - edmozley/freeitsm GitHub Wiki
Dragging a task to another status told nobody
Reported: #262 by pappas81, raised as #266, November 2026 ยท Module: Tasks ยท Fixed in: the release after 4.11.0 (changelog #2760)
What you saw
- On the Tasks board, drag a task you are involved in from one status column to another - say In Progress to Done.
- Nobody else on the task gets a notification. No email goes out, and no workflow that listens for "task status changed" or "task completed" runs.
- Change the status the other way - from the task's own panel, its menu or the right-click menu - and everybody is told as normal.
Dragging is the commonest way a task gets moved, so this hid most status changes from the people who wanted to know.
What was actually wrong
A task's status can be changed through two different pieces of code:
| How | Saves through | Told anybody? |
|---|---|---|
| Panel, menu, right-click | api/tasks/save.php โ TasksService::saveTask() |
โ yes |
| Dragging on the board | api/tasks/reorder.php |
โ no |
REST API POST /tasks/{id}/move |
TasksService::moveTask() |
โ no |
Notifications are not sent by the save code itself. They hang off the workflow engine: WorkflowEngine::dispatch('task.status_changed', ...) first hands the event to the notification router (includes/notifications_router.php), which tells the task's owner and everybody Involved, and then runs any workflows listening for it.
saveTask() dispatches those events. reorder.php and moveTask() write the status, record the history, complete a repeating task and update the calendar - but never dispatched an event. The REST API documentation even called it a deliberate "parity quirk" ("like the UI's drag, /move does not fire task.completed"). It matched the drag, but both were wrong in the same way.
It was two places, not one
The board's drag and the REST API's move had the same gap, so both are fixed, through one shared method.
The fix
A new public method on TasksService announces a move the way saveTask() announces its own changes:
public static function announceStatusMove(PDO $conn, int $taskId, int $oldStatusId, int $newStatusId, bool $completing): void
{
if ($oldStatusId === $newStatusId) return; // a reorder within one column
if ($completing) self::completedDispatch($conn, $taskId);
self::taskEventDispatch($conn, 'task.status_changed', $taskId);
self::calendarOnTaskChanged($conn, $taskId);
}
reorder.php and moveTask() call it after the commit, passing the status from before the move (TaskHistory::before()). The calendar update makes a network call, so it must never run inside the transaction.
- A reorder within one column changes no status, so it announces nothing - nobody is told "the status changed" when it did not.
- Moving to Done fires
task.completedas well astask.status_changed, exactly as ticking it complete does. (The bell hastask.completedswitched off by default, so people are not told twice.) - The person who moved it is not told about their own move - the router already leaves out the actor.
No database change, nothing to run after upgrading.
Files changed
| File | Change |
|---|---|
๐ต includes/services/tasks.php |
new announceStatusMove(); moveTask() calls it |
๐ต api/tasks/reorder.php |
calls it after the drag's commit |
๐งช tests/task-drag-notifies.php |
new: 7 checks over HTTP |
How it was proved
tests/task-drag-notifies.php, against a throwaway install over HTTP: two synthetic analysts sign in for real, one drags the other's task through the real reorder.php.
| Did | Got |
|---|---|
| Dragged To Do โ In Progress | โ the owner gets task status changed |
| Reordered within In Progress | โ nothing new |
| Dragged In Progress โ Done | โ the owner is told again (coalesced into the same notification) |
| The person who dragged it | โ not told about their own move |
The control: with the old reorder.php back in place, four of the seven checks fail - no notification at all.
The REST move was checked the same way through TasksService::moveTask(): one notification for a status change, none for a move within the column.
Still passing: task-evidence (27), task-evidence-atomic (21), calendar-sync-tasks (31) - the move also drives repeating tasks, completion evidence and the calendar.
Not proven here: tests/task-recurrence-spawn.php stops while setting up on a fresh install - it creates tasks without created_by_id, which the schema requires. That is a problem in the test itself, unrelated to this change.
What this means for you
- Dragging a task to another status tells the people on it, exactly as changing the status from its menu always did.
- Workflows that listen for Task status changed or Task completed now run for dragged tasks too - worth knowing if you built one around the old behaviour.
- The same for anything that moves tasks through the REST API.
Related
- Tasks ยท Task board and subtasks - Developer Guide ยท REST API - Tasks
- People on a task - who "everybody involved" is
- Response to #262 ยท Bugs resolved