feat(views): sort tasks by their position relative to the view they're in

This commit is contained in:
kolaente 2024-03-14 22:55:18 +01:00
parent 2502776460
commit d1d07f462c
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
6 changed files with 53 additions and 6 deletions

View File

@ -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

View File

@ -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
// ============

View File

@ -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,
},
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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 {