1
0

fix(filter): make sure tasks are in a correct bucket and position when they are part of a date filter

Whenever a task is part of a date filter, it might fall in or out of a filter bucket without anything changing, other than the current time. For example, a filter condition like due_date > now may include different tasks depending on the current time.
For these kinds of tasks to properly show up in the kanban view of a filter, there has to be an entry in the task_buckets table. These entries only got updated when either a task was updated or the filter itself was updated. To account for th changing of time, we also need to check periodically if tasks are now part or not anymore part of that filter.
This change adds a cron task to do precisely that.
We'll have to see if this works resource-wise, but the cron is not the only one doing a bunch of sql queries so it might be fine after all.

Resolves https://community.vikunja.io/t/tasks-in-saved-filter-appear-in-list-view-but-are-not-visible-in-kanban-view/2800

(cherry picked from commit bc52da4029170bfd10cddea28cf5c6983969cb42)
This commit is contained in:
kolaente 2024-09-19 11:19:48 +02:00
parent d27b62db6e
commit 62c238e4bc
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
3 changed files with 168 additions and 2 deletions

View File

@ -96,6 +96,7 @@ func FullInit() {
models.RegisterOldExportCleanupCron()
openid.CleanupSavedOpenIDProviders()
openid.RegisterEmptyOpenIDTeamCleanupCron()
models.RegisterAddTaskToFilterViewCron()
// Start processing events
go func() {

View File

@ -415,6 +415,6 @@ func RegisterOldExportCleanupCron() {
})
if err != nil {
log.Fatalf("Could not old export cleanup cron: %s", err)
log.Fatalf("Could not register old export cleanup cron: %s", err)
}
}

View File

@ -19,10 +19,12 @@ package models
import (
"time"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web"
"xorm.io/builder"
"xorm.io/xorm"
)
@ -347,3 +349,166 @@ func addTaskToFilter(s *xorm.Session, filter *SavedFilter, view *ProjectView, fa
return
}
func RegisterAddTaskToFilterViewCron() {
const logPrefix = "[Add Task To Filter View Cron] "
err := cron.Schedule("* * * * *", func() {
s := db.NewSession()
defer s.Close()
// Get all filters with a date clause and a manual kanban view
filters := map[int64]*SavedFilter{}
err := s.Where("filters LIKE '%_date%'").Find(&filters)
if err != nil {
log.Errorf("%sError fetching filters: %s", logPrefix, err)
return
}
if len(filters) == 0 {
return
}
filterProjectIDs := []int64{}
for _, f := range filters {
filterProjectIDs = append(filterProjectIDs, getProjectIDFromSavedFilterID(f.ID))
}
kanbanFilterViews := []*ProjectView{}
err = s.And(
builder.Eq{"view_kind": ProjectViewKindKanban},
builder.Eq{"bucket_configuration_mode": BucketConfigurationModeManual},
builder.In("project_id", filterProjectIDs),
).
Find(&kanbanFilterViews)
if err != nil {
log.Errorf("%sError fetching kanban filter views: %s", logPrefix, err)
return
}
if len(kanbanFilterViews) == 0 {
return
}
log.Debugf("%sFound %d kanban filter views with dates", logPrefix, len(kanbanFilterViews))
filterTasksCache := make(map[int64][]*Task)
newTaskBuckets := []*TaskBucket{}
newTaskPositions := []*TaskPosition{}
deleteCond := []builder.Cond{}
for _, view := range kanbanFilterViews {
filterID := getSavedFilterIDFromProjectID(view.ProjectID)
filter, exists := filters[filterID]
if !exists {
log.Debugf("%sDid not find filter for view %d", logPrefix, view.ID)
continue
}
// currently saved
tasks, has := filterTasksCache[filterID]
if !has {
tc := &TaskCollection{
ProjectID: view.ProjectID,
}
resultTasks, _, _, err := tc.ReadAll(s, &user.User{ID: filter.OwnerID}, "", 1, -1)
if err != nil {
log.Errorf("%sError fetching tasks for filter %d: %s", logPrefix, filterID, err)
return
}
tasks = resultTasks.([]*Task)
}
// Get saved tasks in task_buckets and task_positions
savedTaskBuckets := []*TaskBucket{}
err = s.Where("project_view_id = ?", view.ID).Find(&savedTaskBuckets)
if err != nil {
log.Errorf("%sError fetching saved task buckets: %s", logPrefix, err)
continue
}
savedTaskBucketMap := make(map[int64]*TaskBucket)
for _, tb := range savedTaskBuckets {
savedTaskBucketMap[tb.TaskID] = tb
}
savedTaskPositions := []*TaskPosition{}
err = s.Where("project_view_id = ?", view.ID).Find(&savedTaskPositions)
if err != nil {
log.Errorf("%sError fetching saved task positions: %s", logPrefix, err)
continue
}
savedTaskPositionMap := make(map[int64]*TaskPosition)
for _, tp := range savedTaskPositions {
savedTaskPositionMap[tp.TaskID] = tp
}
// Collect new tasks to task_buckets and task_positions
for _, task := range tasks {
if _, exists := savedTaskBucketMap[task.ID]; !exists {
view.DefaultBucketID, err = getDefaultBucketID(s, view)
if err != nil {
log.Errorf("%sError fetching default bucket for view %d: %s", logPrefix, view.ID, err)
continue
}
tb := &TaskBucket{
TaskID: task.ID,
ProjectViewID: view.ID,
BucketID: view.DefaultBucketID,
}
newTaskBuckets = append(newTaskBuckets, tb)
}
if _, exists := savedTaskPositionMap[task.ID]; !exists {
tp := &TaskPosition{
TaskID: task.ID,
ProjectViewID: view.ID,
Position: task.Position,
}
newTaskPositions = append(newTaskPositions, tp)
}
}
// Remove tasks that should not be there
for taskID := range savedTaskBucketMap {
found := false
for _, task := range tasks {
if task.ID == taskID {
found = true
break
}
}
if !found {
deleteCond = append(deleteCond, builder.And(
builder.Eq{"task_id": taskID},
builder.Eq{"project_view_id": view.ID},
))
}
}
}
if len(newTaskBuckets) > 0 {
_, err = s.Insert(newTaskBuckets)
if err != nil {
log.Errorf("%sError inserting task buckets: %s", logPrefix, err)
}
}
if len(newTaskPositions) > 0 {
_, err = s.Insert(newTaskPositions)
if err != nil {
log.Errorf("%sError inserting task positions: %s", logPrefix, err)
}
}
if len(deleteCond) > 0 {
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskBucket{})
if err != nil {
log.Errorf("%sError deleting task buckets: %s", logPrefix, err)
}
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskPosition{})
if err != nil {
log.Errorf("%sError deleting task positions: %s", logPrefix, err)
}
}
})
if err != nil {
log.Fatalf("Could register add task to filter view cron: %s", err)
}
}