Task Position (#412)
Fix misspell Fix sorting tasks with null values Fix sorting by priority for postgres Merge branch 'master' into feature/position Add community link Update golang.org/x/crypto commit hash to 44a6062 (#429) Update golang.org/x/crypto commit hash to 44a6062 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/429 Update module lib/pq to v1.4.0 (#428) Update module lib/pq to v1.4.0 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/428 Fix updating position Add ordering tasks in buckets by position Make task sort by string Merge branch 'master' into feature/position Update golang.org/x/crypto commit hash to 3c4aac8 (#419) Update golang.org/x/crypto commit hash to 3c4aac8 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/419 Merge branch 'master' into feature/position Fix moving tasks back into the empty (ID: 0) bucket Add adding a default position when creating new tasks Update golang.org/x/crypto commit hash to a76a400 (#411) Update golang.org/x/crypto commit hash to a76a400 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/411 Remove unused code Fix tests Add migration for position attribute Add position attribute Co-authored-by: kolaente <k@knt.li> Co-authored-by: renovate <renovatebot@kolaente.de> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/412
This commit is contained in:
@ -16,21 +16,12 @@
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type (
|
||||
sortParam struct {
|
||||
sortBy sortProperty
|
||||
sortBy string
|
||||
orderBy sortOrder // asc or desc
|
||||
}
|
||||
|
||||
sortProperty string
|
||||
|
||||
sortOrder string
|
||||
)
|
||||
|
||||
@ -52,12 +43,9 @@ const (
|
||||
taskPropertyUID string = "uid"
|
||||
taskPropertyCreated string = "created"
|
||||
taskPropertyUpdated string = "updated"
|
||||
taskPropertyPosition string = "position"
|
||||
)
|
||||
|
||||
func (p sortProperty) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
const (
|
||||
orderInvalid sortOrder = "invalid"
|
||||
orderAscending sortOrder = "asc"
|
||||
@ -82,139 +70,5 @@ func (sp *sortParam) validate() error {
|
||||
if sp.orderBy != orderDescending && sp.orderBy != orderAscending {
|
||||
return ErrInvalidSortOrder{OrderBy: sp.orderBy}
|
||||
}
|
||||
return validateTaskField(string(sp.sortBy))
|
||||
}
|
||||
|
||||
type taskComparator func(lhs, rhs *Task) int64
|
||||
|
||||
func mustMakeComparator(fieldName string) taskComparator {
|
||||
field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(fieldName)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Field '%s' has not been found on Task", fieldName))
|
||||
}
|
||||
|
||||
extractProp := func(task *Task) interface{} {
|
||||
return reflect.ValueOf(task).Elem().FieldByIndex(field.Index).Interface()
|
||||
}
|
||||
|
||||
// Special case for handling TimeStamp types
|
||||
if field.Type.Name() == "TimeStamp" {
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
return int64(extractProp(lhs).(timeutil.TimeStamp)) - int64(extractProp(rhs).(timeutil.TimeStamp))
|
||||
}
|
||||
}
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Int64:
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
return extractProp(lhs).(int64) - extractProp(rhs).(int64)
|
||||
}
|
||||
case reflect.Float64:
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
floatLHS, floatRHS := extractProp(lhs).(float64), extractProp(rhs).(float64)
|
||||
if floatLHS > floatRHS {
|
||||
return 1
|
||||
} else if floatLHS < floatRHS {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
case reflect.String:
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
strLHS, strRHS := extractProp(lhs).(string), extractProp(rhs).(string)
|
||||
if strLHS > strRHS {
|
||||
return 1
|
||||
} else if strLHS < strRHS {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
case reflect.Bool:
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
boolLHS, boolRHS := extractProp(lhs).(bool), extractProp(rhs).(bool)
|
||||
if !boolLHS && boolRHS {
|
||||
return -1
|
||||
} else if boolLHS && !boolRHS {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported type for sorting: %s", field.Type.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
// This is a map of properties that can be sorted by
|
||||
// and their appropriate comparator function.
|
||||
// The comparator function sorts in ascending mode.
|
||||
var propertyComparators = map[string]taskComparator{
|
||||
taskPropertyID: mustMakeComparator("ID"),
|
||||
taskPropertyText: mustMakeComparator("Text"),
|
||||
taskPropertyDescription: mustMakeComparator("Description"),
|
||||
taskPropertyDone: mustMakeComparator("Done"),
|
||||
taskPropertyDoneAtUnix: mustMakeComparator("DoneAt"),
|
||||
taskPropertyDueDateUnix: mustMakeComparator("DueDate"),
|
||||
taskPropertyCreatedByID: mustMakeComparator("CreatedByID"),
|
||||
taskPropertyListID: mustMakeComparator("ListID"),
|
||||
taskPropertyRepeatAfter: mustMakeComparator("RepeatAfter"),
|
||||
taskPropertyPriority: mustMakeComparator("Priority"),
|
||||
taskPropertyStartDateUnix: mustMakeComparator("StartDate"),
|
||||
taskPropertyEndDateUnix: mustMakeComparator("EndDate"),
|
||||
taskPropertyHexColor: mustMakeComparator("HexColor"),
|
||||
taskPropertyPercentDone: mustMakeComparator("PercentDone"),
|
||||
taskPropertyUID: mustMakeComparator("UID"),
|
||||
taskPropertyCreated: mustMakeComparator("Created"),
|
||||
taskPropertyUpdated: mustMakeComparator("Updated"),
|
||||
}
|
||||
|
||||
// Creates a taskComparator that sorts by the first comparator and falls back to
|
||||
// the second one (and so on...) if the properties were equal.
|
||||
func combineComparators(comparators ...taskComparator) taskComparator {
|
||||
return func(lhs, rhs *Task) int64 {
|
||||
for _, compare := range comparators {
|
||||
res := compare(lhs, rhs)
|
||||
if res != 0 {
|
||||
return res
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func sortTasks(tasks []*Task, by []*sortParam) {
|
||||
|
||||
// Always sort at least by id asc so we have a consistent order of items every time
|
||||
// If we would not do this, we would get a different order for items with the same content every time
|
||||
// the slice is sorted. To circumvent this, we always order at least by ID.
|
||||
if len(by) == 0 ||
|
||||
(len(by) > 0 && by[len(by)-1].sortBy != sortProperty(taskPropertyID)) { // Don't sort by ID last if the id parameter is already passed as the last parameter.
|
||||
by = append(by, &sortParam{sortBy: sortProperty(taskPropertyID), orderBy: orderAscending})
|
||||
}
|
||||
|
||||
comparators := make([]taskComparator, 0, len(by))
|
||||
for _, param := range by {
|
||||
comparator, ok := propertyComparators[string(param.sortBy)]
|
||||
if !ok {
|
||||
panic("No suitable comparator for sortBy found! Param was " + param.sortBy)
|
||||
}
|
||||
|
||||
// This is a descending sort, so we need to negate the comparator (i.e. switch the inputs).
|
||||
if param.orderBy == orderDescending {
|
||||
oldComparator := comparator
|
||||
comparator = func(lhs, rhs *Task) int64 {
|
||||
return oldComparator(lhs, rhs) * -1
|
||||
}
|
||||
}
|
||||
|
||||
comparators = append(comparators, comparator)
|
||||
}
|
||||
|
||||
combinedComparator := combineComparators(comparators...)
|
||||
|
||||
sort.Slice(tasks, func(i, j int) bool {
|
||||
lhs, rhs := tasks[i], tasks[j]
|
||||
|
||||
res := combinedComparator(lhs, rhs)
|
||||
return res <= 0
|
||||
})
|
||||
return validateTaskField(sp.sortBy)
|
||||
}
|
||||
|
Reference in New Issue
Block a user