Add parsing filter query parameters into task filters
continuous-integration/drone/pr Build is failing Details

Signed-off-by: kolaente <k@knt.li>
This commit is contained in:
kolaente 2020-04-07 20:58:33 +02:00
parent 3842feef49
commit cbb7c53dd1
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
5 changed files with 183 additions and 44 deletions

View File

@ -653,6 +653,60 @@ func (err ErrTaskCommentDoesNotExist) HTTPError() web.HTTPError {
}
}
// ErrInvalidTaskField represents an error where the provided task field is invalid
type ErrInvalidTaskField struct {
TaskField string
}
// IsErrInvalidTaskField checks if an error is ErrInvalidTaskField.
func IsErrInvalidTaskField(err error) bool {
_, ok := err.(ErrInvalidTaskField)
return ok
}
func (err ErrInvalidTaskField) Error() string {
return fmt.Sprintf("Task Field is invalid [TaskField: %s]", err.TaskField)
}
// ErrCodeInvalidTaskField holds the unique world-error code of this error
const ErrCodeInvalidTaskField = 4016
// HTTPError holds the http error description
func (err ErrInvalidTaskField) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeInvalidTaskField,
Message: fmt.Sprintf("The task field '%s' is invalid.", err.TaskField),
}
}
// ErrInvalidTaskFilterComparator represents an error where the provided task field is invalid
type ErrInvalidTaskFilterComparator struct {
Comparator taskFilterComparator
}
// IsErrInvalidTaskFilterComparator checks if an error is ErrInvalidTaskFilterComparator.
func IsErrInvalidTaskFilterComparator(err error) bool {
_, ok := err.(ErrInvalidTaskFilterComparator)
return ok
}
func (err ErrInvalidTaskFilterComparator) Error() string {
return fmt.Sprintf("Task filter comparator is invalid [Comparator: %s]", err.Comparator)
}
// ErrCodeInvalidTaskFilterComparator holds the unique world-error code of this error
const ErrCodeInvalidTaskFilterComparator = 4017
// HTTPError holds the http error description
func (err ErrInvalidTaskFilterComparator) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeInvalidTaskFilterComparator,
Message: fmt.Sprintf("The task filter comparator '%s' is invalid.", err.Comparator),
}
}
// =================
// Namespace errors
// =================

View File

@ -43,6 +43,9 @@ type TaskCollection struct {
// The value of the field name to filter by
FilterValue []string `query:"filter_value"`
FilterValueArr []string `query:"filter_value[]"`
// The comparator for field and value
FilterComparator []string `query:"filter_comparator"`
FilterComparatorArr []string `query:"filter_comparator[]"`
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`
@ -70,7 +73,7 @@ func validateTaskField(fieldName string) error {
taskPropertyUpdated:
return nil
}
return ErrInvalidSortParam{SortBy: sp.sortBy}
return ErrInvalidTaskField{TaskField: fieldName}
}
@ -110,6 +113,10 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
tf.FilterValue = append(tf.FilterValue, tf.FilterValueArr...)
}
if len(tf.FilterComparatorArr) > 0 {
tf.FilterValue = append(tf.FilterValue, tf.FilterComparatorArr...)
}
var sort = make([]*sortParam, 0, len(tf.SortBy))
for i, s := range tf.SortBy {
param := &sortParam{
@ -128,6 +135,31 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
sort = append(sort, param)
}
var filters = make([]*taskFilter, 0, len(tf.FilterBy))
for i, f := range tf.FilterBy {
filter := &taskFilter{
field: f,
}
if len(tf.FilterValue) > i {
filter.value = tf.FilterValue[i]
}
if len(tf.FilterComparator) > i {
filter.comparator, err = getFilterComparatorFromString(tf.FilterComparator[i])
if err != nil {
return nil, 0, 0, err
}
}
err = filter.validate()
if err != nil {
return nil, 0, 0, err
}
filters = append(filters, filter)
}
taskopts := &taskOptions{
search: search,
startDate: time.Unix(tf.StartDateSortUnix, 0),
@ -135,6 +167,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
page: page,
perPage: perPage,
sortby: sort,
filters: filters,
}
shareAuth, is := a.(*LinkSharing)

View File

@ -0,0 +1,72 @@
// Copyright 2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package models
type taskFilterComparator string
const (
taskFilterComparatorInvalid taskFilterComparator = "invalid"
taskFilterComparatorEquals taskFilterComparator = "="
taskFilterComparatorGreater taskFilterComparator = ">"
taskFilterComparatorGreateEquals taskFilterComparator = ">="
taskFilterComparatorLess taskFilterComparator = "<"
taskFilterComparatorLessEquals taskFilterComparator = "<="
)
type taskFilter struct {
field string
value string
comparator taskFilterComparator
}
func (tf *taskFilter) validate() (err error) {
err = validateTaskField(tf.field)
if err != nil {
return
}
switch tf.comparator {
case
taskFilterComparatorEquals,
taskFilterComparatorGreater,
taskFilterComparatorGreateEquals,
taskFilterComparatorLess,
taskFilterComparatorLessEquals:
return nil
default:
return ErrInvalidTaskFilterComparator{Comparator: tf.comparator}
}
}
func getFilterComparatorFromString(comparator string) (taskFilterComparator, error) {
switch comparator {
case "equals":
return taskFilterComparatorEquals, nil
case "greater":
return taskFilterComparatorGreater, nil
case "greater_equals":
return taskFilterComparatorGreateEquals, nil
case "less":
return taskFilterComparatorLess, nil
case "less_equals":
return taskFilterComparatorLessEquals, nil
default:
return taskFilterComparatorInvalid, ErrInvalidTaskFilterComparator{Comparator: taskFilterComparator(comparator)}
}
}

View File

@ -35,23 +35,23 @@ type (
)
const (
taskPropertyID sortProperty = "id"
taskPropertyText sortProperty = "text"
taskPropertyDescription sortProperty = "description"
taskPropertyDone sortProperty = "done"
taskPropertyDoneAtUnix sortProperty = "done_at_unix"
taskPropertyDueDateUnix sortProperty = "due_date_unix"
taskPropertyCreatedByID sortProperty = "created_by_id"
taskPropertyListID sortProperty = "list_id"
taskPropertyRepeatAfter sortProperty = "repeat_after"
taskPropertyPriority sortProperty = "priority"
taskPropertyStartDateUnix sortProperty = "start_date_unix"
taskPropertyEndDateUnix sortProperty = "end_date_unix"
taskPropertyHexColor sortProperty = "hex_color"
taskPropertyPercentDone sortProperty = "percent_done"
taskPropertyUID sortProperty = "uid"
taskPropertyCreated sortProperty = "created"
taskPropertyUpdated sortProperty = "updated"
taskPropertyID string = "id"
taskPropertyText string = "text"
taskPropertyDescription string = "description"
taskPropertyDone string = "done"
taskPropertyDoneAtUnix string = "done_at_unix"
taskPropertyDueDateUnix string = "due_date_unix"
taskPropertyCreatedByID string = "created_by_id"
taskPropertyListID string = "list_id"
taskPropertyRepeatAfter string = "repeat_after"
taskPropertyPriority string = "priority"
taskPropertyStartDateUnix string = "start_date_unix"
taskPropertyEndDateUnix string = "end_date_unix"
taskPropertyHexColor string = "hex_color"
taskPropertyPercentDone string = "percent_done"
taskPropertyUID string = "uid"
taskPropertyCreated string = "created"
taskPropertyUpdated string = "updated"
)
func (p sortProperty) String() string {
@ -82,28 +82,7 @@ func (sp *sortParam) validate() error {
if sp.orderBy != orderDescending && sp.orderBy != orderAscending {
return ErrInvalidSortOrder{OrderBy: sp.orderBy}
}
switch sp.sortBy {
case
taskPropertyID,
taskPropertyText,
taskPropertyDescription,
taskPropertyDone,
taskPropertyDoneAtUnix,
taskPropertyDueDateUnix,
taskPropertyCreatedByID,
taskPropertyListID,
taskPropertyRepeatAfter,
taskPropertyPriority,
taskPropertyStartDateUnix,
taskPropertyEndDateUnix,
taskPropertyHexColor,
taskPropertyPercentDone,
taskPropertyUID,
taskPropertyCreated,
taskPropertyUpdated:
return nil
}
return ErrInvalidSortParam{SortBy: sp.sortBy}
return validateTaskField(string(sp.sortBy))
}
type taskComparator func(lhs, rhs *Task) int64
@ -168,7 +147,7 @@ func mustMakeComparator(fieldName string) taskComparator {
// 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[sortProperty]taskComparator{
var propertyComparators = map[string]taskComparator{
taskPropertyID: mustMakeComparator("ID"),
taskPropertyText: mustMakeComparator("Text"),
taskPropertyDescription: mustMakeComparator("Description"),
@ -208,13 +187,13 @@ func sortTasks(tasks []*Task, by []*sortParam) {
// 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 != taskPropertyID) { // Don't sort by ID last if the id parameter is already passed as the last parameter.
by = append(by, &sortParam{sortBy: taskPropertyID, orderBy: orderAscending})
(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[param.sortBy]
comparator, ok := propertyComparators[string(param.sortBy)]
if !ok {
panic("No suitable comparator for sortBy found! Param was " + param.sortBy)
}

View File

@ -116,6 +116,7 @@ type taskOptions struct {
page int
perPage int
sortby []*sortParam
filters []*taskFilter
}
// ReadAll is a dummy function to still have that endpoint documented