From 398c9f10565f1823ee9fa39132ebc1d38fa9ce32 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 15 Mar 2024 23:31:28 +0100 Subject: [PATCH] fix(views): return tasks in their buckets --- pkg/models/error.go | 18 ++++++++-------- pkg/models/kanban.go | 6 +++++- pkg/models/task_search.go | 24 ++++++++++++++++------ pkg/models/tasks.go | 43 ++++++++++++++++++--------------------- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/pkg/models/error.go b/pkg/models/error.go index e63f6fc00..99a18349f 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -1527,27 +1527,27 @@ func (err ErrBucketDoesNotExist) HTTPError() web.HTTPError { } } -// ErrBucketDoesNotBelongToProject represents an error where a kanban bucket does not belong to a project -type ErrBucketDoesNotBelongToProject struct { - BucketID int64 - ProjectID int64 +// ErrBucketDoesNotBelongToProjectView represents an error where a kanban bucket does not belong to a project +type ErrBucketDoesNotBelongToProjectView struct { + BucketID int64 + ProjectViewID int64 } -// IsErrBucketDoesNotBelongToProject checks if an error is ErrBucketDoesNotBelongToProject. +// IsErrBucketDoesNotBelongToProject checks if an error is ErrBucketDoesNotBelongToProjectView. func IsErrBucketDoesNotBelongToProject(err error) bool { - _, ok := err.(ErrBucketDoesNotBelongToProject) + _, ok := err.(ErrBucketDoesNotBelongToProjectView) return ok } -func (err ErrBucketDoesNotBelongToProject) Error() string { - return fmt.Sprintf("Bucket does not not belong to project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID) +func (err ErrBucketDoesNotBelongToProjectView) Error() string { + return fmt.Sprintf("Bucket does not not belong to project view [BucketID: %d, ProjectViewID: %d]", err.BucketID, err.ProjectViewID) } // ErrCodeBucketDoesNotBelongToProject holds the unique world-error code of this error const ErrCodeBucketDoesNotBelongToProject = 10002 // HTTPError holds the http error description -func (err ErrBucketDoesNotBelongToProject) HTTPError() web.HTTPError { +func (err ErrBucketDoesNotBelongToProjectView) HTTPError() web.HTTPError { return web.HTTPError{ HTTPCode: http.StatusBadRequest, Code: ErrCodeBucketDoesNotBelongToProject, diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 9cf024ea1..dc9d38a13 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -213,7 +213,7 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, opts *taskSear opts.sortby = []*sortParam{ { - projectViewID: view.ProjectID, + projectViewID: view.ID, orderBy: orderAscending, sortBy: taskPropertyPosition, }, @@ -260,6 +260,10 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, opts *taskSear return nil, err } + for _, t := range ts { + t.BucketID = bucket.ID + } + bucket.Count = total tasks = append(tasks, ts...) diff --git a/pkg/models/task_search.go b/pkg/models/task_search.go index 221721328..ba84af68a 100644 --- a/pkg/models/task_search.go +++ b/pkg/models/task_search.go @@ -209,6 +209,14 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo return nil, 0, err } + var joinTaskBuckets bool + for _, filter := range opts.parsedFilters { + if filter.field == taskPropertyBucketID { + joinTaskBuckets = true + break + } + } + filterCond, err := convertFiltersToDBFilterCond(opts.parsedFilters, opts.filterIncludeNulls) if err != nil { return nil, 0, err @@ -265,20 +273,24 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo } } + if joinTaskBuckets { + query = query.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id") + } + tasks = []*Task{} - err = query.OrderBy(orderby).Find(&tasks) + err = query. + OrderBy(orderby). + Find(&tasks) if err != nil { return nil, totalCount, err } queryCount := d.s.Where(cond) + if joinTaskBuckets { + queryCount = queryCount.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id") + } totalCount, err = queryCount. Count(&Task{}) - if err != nil { - return nil, totalCount, err - - } - return } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 074086e64..7dbd88f6a 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -114,7 +114,7 @@ type Task struct { // The bucket id. Will only be populated when the task is accessed via a view with buckets. // Can be used to move a task between buckets. In that case, the new bucket must be in the same view as the old one. - BucketID int64 `xorm:"bigint null" json:"bucket_id"` + BucketID int64 `xorm:"<-" json:"bucket_id"` // The position of the task - any task project can be sorted as usual by this parameter. // When accessing tasks via views with buckets, this is primarily used to sort them based on a range. @@ -204,6 +204,9 @@ func (t *Task) ReadAll(_ *xorm.Session, _ web.Auth, _ string, _ int, _ int) (res func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err error) { field := "`" + f.field + "`" + if f.field == taskPropertyBucketID { + field = "task_buckets.`bucket_id`" + } switch f.comparator { case taskFilterComparatorEquals: cond = &builder.Eq{field: f.value} @@ -633,15 +636,16 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) { } // Contains all the task logic to figure out what bucket to use for this task. -func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, view *ProjectView, targetBucket *TaskBucket) (err error) { +func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, view *ProjectView, targetBucketID int64) (err error) { + if view.BucketConfigurationMode == BucketConfigurationModeNone { + return + } var shouldChangeBucket = true - if targetBucket == nil { - targetBucket = &TaskBucket{ - BucketID: 0, - TaskID: task.ID, - ProjectViewID: view.ID, - } + targetBucket := &TaskBucket{ + BucketID: targetBucketID, + TaskID: task.ID, + ProjectViewID: view.ID, } oldTaskBucket := &TaskBucket{} @@ -678,15 +682,12 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, view *Projec } // If there is a bucket set, make sure they belong to the same project as the task - if task.ProjectID != bucket.ProjectID { - return ErrBucketDoesNotBelongToProject{ - ProjectID: task.ProjectID, - BucketID: bucket.ID, + if view.ID != bucket.ProjectViewID { + return ErrBucketDoesNotBelongToProjectView{ + ProjectViewID: view.ID, + BucketID: bucket.ID, } } - if err != nil { - return - } // Check the bucket limit // Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket @@ -811,7 +812,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err for _, view := range views { // Get the default bucket and move the task there - err = setTaskBucket(s, t, nil, view, &TaskBucket{}) + err = setTaskBucket(s, t, nil, view, 0) if err != nil { return } @@ -913,20 +914,16 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { Find(&buckets) for _, view := range views { - taskBucket := &TaskBucket{ - TaskID: t.ID, - ProjectViewID: view.ID, - } - // Only update the bucket when the current view + var targetBucketID int64 if t.BucketID != 0 { bucket, has := buckets[t.BucketID] if has && bucket.ProjectViewID == view.ID { - taskBucket.BucketID = t.BucketID + targetBucketID = t.BucketID } } - err = setTaskBucket(s, t, &ot, view, taskBucket) + err = setTaskBucket(s, t, &ot, view, targetBucketID) if err != nil { return err }