fix: fall back to full recalculation when updating a task position

updateTaskPosition returned ErrNeedsFullRecalculation raw when the
neighbours around the target position were too close for a localized
repair, surfacing as an HTTP 500 on POST /api/v1/tasks/{id}/position.
It now falls back to a full recalculation of the view, like the task
creation path and the CLI repair command already do.

Ref: Sentry API-CLOUD-1R
This commit is contained in:
kolaente
2026-09-05 12:17:02 +02:00
parent d388b9d6b9
commit 206975997f
2 changed files with 49 additions and 0 deletions
+3
View File
@@ -277,6 +277,9 @@ func updateTaskPosition(s *xorm.Session, a web.Auth, tp *TaskPosition) (err erro
if len(conflicts) > 1 {
err = resolveTaskPositionConflicts(s, tp.ProjectViewID, conflicts)
if IsErrNeedsFullRecalculation(err) {
err = recalculateTaskPositionsForRepair(s, &ProjectView{ID: tp.ProjectViewID})
}
if err != nil {
return err
}
+46
View File
@@ -315,6 +315,52 @@ func TestUpdateTaskPositionWithConflictResolution(t *testing.T) {
assert.NotEqual(t, pos1.Position, pos2.Position)
})
t.Run("falls back to full recalculation when neighbours are too close", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
_, err := s.Where("project_view_id = ?", 93).Delete(&TaskPosition{})
require.NoError(t, err)
// The gap between the neighbours of the target position only leaves
// 0.01/3 for two conflicting tasks, less than MinPositionSpacing.
conflictPosition := 100.005
_, err = s.Insert(&TaskPosition{TaskID: 900, ProjectViewID: 93, Position: 100})
require.NoError(t, err)
_, err = s.Insert(&TaskPosition{TaskID: 901, ProjectViewID: 93, Position: conflictPosition})
require.NoError(t, err)
_, err = s.Insert(&TaskPosition{TaskID: 902, ProjectViewID: 93, Position: 100.01})
require.NoError(t, err)
tp := &TaskPosition{
TaskID: 903,
ProjectViewID: 93,
Position: conflictPosition,
}
err = updateTaskPosition(s, nil, tp)
require.NoError(t, err)
var positions []*TaskPosition
err = s.Where("project_view_id = ?", 93).OrderBy("position ASC").Find(&positions)
require.NoError(t, err)
require.Len(t, positions, 4)
for i := 1; i < len(positions); i++ {
assert.Greater(t, positions[i].Position, positions[i-1].Position,
"position of task %d should be greater than the one of task %d",
positions[i].TaskID, positions[i-1].TaskID)
assert.GreaterOrEqual(t, positions[i].Position-positions[i-1].Position, MinPositionSpacing)
}
updated := &TaskPosition{}
_, err = s.Where("task_id = ? AND project_view_id = ?", 903, 93).Get(updated)
require.NoError(t, err)
assert.InDelta(t, updated.Position, tp.Position, 0, "the returned position should be refreshed")
assert.Greater(t, tp.Position, 1000.0, "positions should have been spread over the full range")
})
}
func TestRepairTaskPositions(t *testing.T) {