diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md index b2b26225c..8183f44c3 100644 --- a/docs/content/doc/usage/errors.md +++ b/docs/content/doc/usage/errors.md @@ -99,6 +99,7 @@ This document describes the different errors Vikunja can return. | 4023 | 409 | Tried to create a task relation which would create a cycle. | | 4024 | 400 | The provided filter expression is invalid. | | 4025 | 400 | The reaction kind is invalid. | +| 4026 | 400 | You must provide a project view ID when sorting by position. | ## Team diff --git a/pkg/models/error.go b/pkg/models/error.go index 1b54b955e..f2f05a2d6 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -1114,6 +1114,25 @@ func (err ErrInvalidReactionEntityKind) HTTPError() web.HTTPError { } } +// ErrMustHaveProjectViewToSortByPosition represents an error where no project view id was supplied +type ErrMustHaveProjectViewToSortByPosition struct{} + +func (err ErrMustHaveProjectViewToSortByPosition) Error() string { + return "You must provide a project view ID when sorting by position" +} + +// ErrCodeMustHaveProjectViewToSortByPosition holds the unique world-error code of this error +const ErrCodeMustHaveProjectViewToSortByPosition = 4026 + +// HTTPError holds the http error description +func (err ErrMustHaveProjectViewToSortByPosition) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeMustHaveProjectViewToSortByPosition, + Message: "You must provide a project view ID when sorting by position", + } +} + // ============ // Team errors // ============ diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 97e734a15..671d23fbc 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -200,8 +200,9 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, opts *taskSear opts.sortby = []*sortParam{ { - orderBy: orderAscending, - sortBy: taskPropertyPosition, + projectViewID: view.ProjectID, + orderBy: orderAscending, + sortBy: taskPropertyPosition, }, } diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 9b3a2d73d..4ef81e976 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -193,6 +193,14 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa opts.page = page opts.perPage = perPage + if view != nil { + opts.sortby = append(opts.sortby, &sortParam{ + projectViewID: view.ID, + sortBy: taskPropertyPosition, + orderBy: orderAscending, + }) + } + shareAuth, is := a.(*LinkSharing) if is { project, err := GetProjectSimpleByID(s, shareAuth.ProjectID) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 4777b3bfe..fb8b797f2 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -18,8 +18,9 @@ package models type ( sortParam struct { - sortBy string - orderBy sortOrder // asc or desc + sortBy string + orderBy sortOrder // asc or desc + projectViewID int64 } sortOrder string @@ -72,5 +73,10 @@ func (sp *sortParam) validate() error { if sp.orderBy != orderDescending && sp.orderBy != orderAscending { return ErrInvalidSortOrder{OrderBy: sp.orderBy} } + + if sp.sortBy == taskPropertyPosition && sp.projectViewID == 0 { + return ErrMustHaveProjectViewToSortByPosition{} + } + return validateTaskField(sp.sortBy) } diff --git a/pkg/models/task_search.go b/pkg/models/task_search.go index 66949dbfb..6b0b4ad2f 100644 --- a/pkg/models/task_search.go +++ b/pkg/models/task_search.go @@ -53,14 +53,19 @@ func getOrderByDBStatement(opts *taskSearchOptions) (orderby string, err error) return "", err } + var prefix string + if param.sortBy == taskPropertyPosition { + prefix = "task_positions." + } + // Mysql sorts columns with null values before ones without null value. // Because it does not have support for NULLS FIRST or NULLS LAST we work around this by // first sorting for null (or not null) values and then the order we actually want to. if db.Type() == schemas.MYSQL { - orderby += "`" + param.sortBy + "` IS NULL, " + orderby += prefix + "`" + param.sortBy + "` IS NULL, " } - orderby += "`" + param.sortBy + "` " + param.orderBy.String() + orderby += prefix + "`" + param.sortBy + "` " + param.orderBy.String() // Postgres and sqlite allow us to control how columns with null values are sorted. // To make that consistent with the sort order we have and other dbms, we're adding a separate clause here. @@ -253,6 +258,13 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo query = query.Limit(limit, start) } + for _, param := range opts.sortby { + if param.sortBy == taskPropertyPosition { + query = query.Join("LEFT", "task_positions", "task_positions.task_id = tasks.id AND task_positions.project_view_id = ?", param.projectViewID) + break + } + } + tasks = []*Task{} err = query.OrderBy(orderby).Find(&tasks) if err != nil {