Add task filter for assignees (#746)

Update docs

Add filter by assignee

Fix string values

Update docs

Update docs

Make "in" task filter actually work

Add "in" filter type

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/746
Co-Authored-By: konrad <konrad@kola-entertainments.de>
Co-Committed-By: konrad <konrad@kola-entertainments.de>
This commit is contained in:
konrad 2020-12-19 21:39:11 +00:00
parent 92bcce3f7c
commit 18325e964d
8 changed files with 169 additions and 55 deletions

View file

@ -171,6 +171,7 @@
namespace_id: 14
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
# User 1 does not have access to this list
-
id: 20
title: Test20

View file

@ -91,9 +91,9 @@ func validateTaskField(fieldName string) error {
// @Param s query string false "Search tasks by task text."
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
// @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties except `labels`, `assignees`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_value query string false "The value to filter for."
// @Param filter_comparator query string false "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals` and `like`. Defaults to `equals`"
// @Param filter_comparator query string false "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`"
// @Param filter_concat query string false "The concatinator to use for filters. Available values are `and` or `or`. Defaults to `or`."
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
// @Security JWTKeyAuth

View file

@ -21,6 +21,7 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"code.vikunja.io/api/pkg/config"
@ -40,6 +41,7 @@ const (
taskFilterComparatorLessEquals taskFilterComparator = "<="
taskFilterComparatorNotEquals taskFilterComparator = "!="
taskFilterComparatorLike taskFilterComparator = "like"
taskFilterComparatorIn taskFilterComparator = "in"
)
type taskFilter struct {
@ -89,7 +91,7 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err
// Cast the field value to its native type
if len(c.FilterValue) > i {
filter.value, err = getNativeValueForTaskField(filter.field, c.FilterValue[i])
filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, c.FilterValue[i])
if err != nil {
return nil, ErrInvalidTaskFilterValue{
Value: filter.field,
@ -113,7 +115,8 @@ func validateTaskFieldComparator(comparator taskFilterComparator) error {
taskFilterComparatorLess,
taskFilterComparatorLessEquals,
taskFilterComparatorNotEquals,
taskFilterComparatorLike:
taskFilterComparatorLike,
taskFilterComparatorIn:
return nil
case taskFilterComparatorInvalid:
fallthrough
@ -138,42 +141,75 @@ func getFilterComparatorFromString(comparator string) (taskFilterComparator, err
return taskFilterComparatorNotEquals, nil
case "like":
return taskFilterComparatorLike, nil
case "in":
return taskFilterComparatorIn, nil
default:
return taskFilterComparatorInvalid, ErrInvalidTaskFilterComparator{Comparator: taskFilterComparator(comparator)}
}
}
func getNativeValueForTaskField(fieldName, value string) (nativeValue interface{}, err error) {
field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(strcase.ToCamel(fieldName))
if !ok {
return nil, ErrInvalidTaskField{TaskField: fieldName}
}
func getValueForField(field reflect.StructField, rawValue string) (value interface{}, err error) {
switch field.Type.Kind() {
case reflect.Int64:
nativeValue, err = strconv.ParseInt(value, 10, 64)
value, err = strconv.ParseInt(rawValue, 10, 64)
case reflect.Float64:
nativeValue, err = strconv.ParseFloat(value, 64)
value, err = strconv.ParseFloat(rawValue, 64)
case reflect.String:
nativeValue = value
value = rawValue
case reflect.Bool:
nativeValue, err = strconv.ParseBool(value)
value, err = strconv.ParseBool(rawValue)
case reflect.Struct:
if field.Type == schemas.TimeType {
nativeValue, err = time.Parse(time.RFC3339, value)
nativeValue = nativeValue.(time.Time).In(config.GetTimeZone())
value, err = time.Parse(time.RFC3339, rawValue)
value = value.(time.Time).In(config.GetTimeZone())
}
case reflect.Slice:
t := reflect.SliceOf(schemas.TimeType)
if t != nil {
nativeValue, err = time.Parse(time.RFC3339, value)
nativeValue = nativeValue.(time.Time).In(config.GetTimeZone())
// If this is a slice of pointers we're dealing with some property which is a relation
// In that case we don't really care about what the actual type is, we just cast the value to an
// int64 since we need the id - yes, this assumes we only ever have int64 IDs, but this is fine.
if field.Type.Elem().Kind() == reflect.Ptr {
value, err = strconv.ParseInt(rawValue, 10, 64)
return
}
// There are probably better ways to do this - please let me know if you have one.
if field.Type.Elem().String() == "time.Time" {
value, err = time.Parse(time.RFC3339, rawValue)
value = value.(time.Time).In(config.GetTimeZone())
return
}
fallthrough
default:
panic(fmt.Errorf("unrecognized filter type %s for field %s, value %s", field.Type.String(), fieldName, value))
panic(fmt.Errorf("unrecognized filter type %s for field %s, value %s", field.Type.String(), field.Name, value))
}
return
}
func getNativeValueForTaskField(fieldName string, comparator taskFilterComparator, value string) (nativeValue interface{}, err error) {
var realFieldName = strcase.ToCamel(fieldName)
if strings.ToLower(fieldName) == "id" {
realFieldName = "ID"
}
field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(realFieldName)
if !ok {
return nil, ErrInvalidTaskField{TaskField: fieldName}
}
if comparator == taskFilterComparatorIn {
vals := strings.Split(value, ",")
valueSlice := []interface{}{}
for _, val := range vals {
v, err := getValueForField(field, val)
if err != nil {
return nil, err
}
valueSlice = append(valueSlice, v)
}
return valueSlice, nil
}
return getValueForField(field, value)
}

View file

@ -892,6 +892,46 @@ func TestTaskCollection_ReadAll(t *testing.T) {
},
wantErr: false,
},
{
name: "filter in",
fields: fields{
FilterBy: []string{"id"},
FilterValue: []string{"1,2,34"}, // Task 34 is forbidden for user 1
FilterComparator: []string{"in"},
},
args: defaultArgs,
want: []*Task{
task1,
task2,
},
wantErr: false,
},
{
name: "filter assignees",
fields: fields{
FilterBy: []string{"assignees"},
FilterValue: []string{"1"},
FilterComparator: []string{"equals"},
},
args: defaultArgs,
want: []*Task{
task30,
},
wantErr: false,
},
{
name: "filter assignees in",
fields: fields{
FilterBy: []string{"assignees"},
FilterValue: []string{"1,2"},
FilterComparator: []string{"in"},
},
args: defaultArgs,
want: []*Task{
task30,
},
wantErr: false,
},
}
for _, tt := range tests {

View file

@ -142,12 +142,13 @@ type taskOptions struct {
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text."
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
// @Param filter_by query string false "The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_value query string false "The value to filter for."
// @Param filter_comparator query string false "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less` and `less_equals`. Defaults to `equals`"
// @Param filter_comparator query string false "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`"
// @Param filter_concat query string false "The concatinator to use for filters. Available values are `and` or `or`. Defaults to `or`."
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
// @Security JWTKeyAuth
// @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error"
@ -176,6 +177,8 @@ func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err err
return nil, ErrInvalidTaskFilterValue{Field: f.field, Value: f.value}
}
cond = &builder.Like{f.field, "%" + val + "%"}
case taskFilterComparatorIn:
cond = builder.In(f.field, f.value)
case taskFilterComparatorInvalid:
// Nothing to do
}
@ -187,6 +190,24 @@ func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err err
return
}
func getFilterCondForSeparateTable(table string, concat taskFilterConcatinator, conds []builder.Cond) builder.Cond {
var filtercond builder.Cond
if concat == filterConcatOr {
filtercond = builder.Or(conds...)
}
if concat == filterConcatAnd {
filtercond = builder.And(conds...)
}
return builder.In(
"id",
builder.
Select("task_id").
From(table).
Where(filtercond),
)
}
//nolint:gocyclo
func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
@ -246,8 +267,9 @@ func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []
}
}
// Reminder filters need a special treatment since they are in a separate database
// Some filters need a special treatment since they are in a separate table
reminderFilters := []builder.Cond{}
assigneeFilters := []builder.Cond{}
var filters = make([]builder.Cond, 0, len(opts.filters))
// To still find tasks with nil values, we exclude 0s when comparing with >/< values.
@ -262,6 +284,16 @@ func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []
continue
}
if f.field == "assignees" {
f.field = "user_id"
filter, err := getFilterCond(f, opts.filterIncludeNulls)
if err != nil {
return nil, 0, 0, err
}
assigneeFilters = append(assigneeFilters, filter)
continue
}
filter, err := getFilterCond(f, opts.filterIncludeNulls)
if err != nil {
return nil, 0, 0, err
@ -315,22 +347,11 @@ func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []
}
if len(reminderFilters) > 0 {
var filtercond builder.Cond
if opts.filterConcat == filterConcatOr {
filtercond = builder.Or(reminderFilters...)
filters = append(filters, getFilterCondForSeparateTable("task_reminders", opts.filterConcat, reminderFilters))
}
if opts.filterConcat == filterConcatAnd {
filtercond = builder.And(reminderFilters...)
}
reminderFilter := builder.In(
"id",
builder.
Select("task_id").
From("task_reminders").
Where(filtercond),
)
filters = append(filters, reminderFilter)
if len(assigneeFilters) > 0 {
filters = append(filters, getFilterCondForSeparateTable("task_assignees", opts.filterConcat, assigneeFilters))
}
query = query.Where(listCond)

View file

@ -1892,7 +1892,7 @@ var doc = `{
},
{
"type": "string",
"description": "The name of the field to filter by. Allowed values are all task properties except ` + "`" + `labels` + "`" + `, ` + "`" + `assignees` + "`" + `, ` + "`" + `list` + "`" + ` and ` + "`" + `namespace` + "`" + `. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"description": "The name of the field to filter by. Allowed values are all task properties except ` + "`" + `labels` + "`" + `, ` + "`" + `list` + "`" + ` and ` + "`" + `namespace` + "`" + `. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"name": "filter_by",
"in": "query"
},
@ -1904,7 +1904,7 @@ var doc = `{
},
{
"type": "string",
"description": "The comparator to use for a filter. Available values are ` + "`" + `equals` + "`" + `, ` + "`" + `greater` + "`" + `, ` + "`" + `greater_equals` + "`" + `, ` + "`" + `less` + "`" + `, ` + "`" + `less_equals` + "`" + ` and ` + "`" + `like` + "`" + `. Defaults to ` + "`" + `equals` + "`" + `",
"description": "The comparator to use for a filter. Available values are ` + "`" + `equals` + "`" + `, ` + "`" + `greater` + "`" + `, ` + "`" + `greater_equals` + "`" + `, ` + "`" + `less` + "`" + `, ` + "`" + `less_equals` + "`" + `, ` + "`" + `like` + "`" + ` and ` + "`" + `in` + "`" + `. ` + "`" + `in` + "`" + ` expects comma-separated values in ` + "`" + `filter_value` + "`" + `. Defaults to ` + "`" + `equals` + "`" + `",
"name": "filter_comparator",
"in": "query"
},
@ -3994,7 +3994,7 @@ var doc = `{
},
{
"type": "string",
"description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with ` + "`" + `order_by` + "`" + `. Possible values to sort by are ` + "`" + `id` + "`" + `, ` + "`" + `text` + "`" + `, ` + "`" + `description` + "`" + `, ` + "`" + `done` + "`" + `, ` + "`" + `done_at` + "`" + `, ` + "`" + `due_date` + "`" + `, ` + "`" + `created_by_id` + "`" + `, ` + "`" + `list_id` + "`" + `, ` + "`" + `repeat_after` + "`" + `, ` + "`" + `priority` + "`" + `, ` + "`" + `start_date` + "`" + `, ` + "`" + `end_date` + "`" + `, ` + "`" + `hex_color` + "`" + `, ` + "`" + `percent_done` + "`" + `, ` + "`" + `uid` + "`" + `, ` + "`" + `created` + "`" + `, ` + "`" + `updated` + "`" + `. Default is ` + "`" + `id` + "`" + `.",
"description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with ` + "`" + `order_by` + "`" + `. Possible values to sort by are ` + "`" + `id` + "`" + `, ` + "`" + `title` + "`" + `, ` + "`" + `description` + "`" + `, ` + "`" + `done` + "`" + `, ` + "`" + `done_at` + "`" + `, ` + "`" + `due_date` + "`" + `, ` + "`" + `created_by_id` + "`" + `, ` + "`" + `list_id` + "`" + `, ` + "`" + `repeat_after` + "`" + `, ` + "`" + `priority` + "`" + `, ` + "`" + `start_date` + "`" + `, ` + "`" + `end_date` + "`" + `, ` + "`" + `hex_color` + "`" + `, ` + "`" + `percent_done` + "`" + `, ` + "`" + `uid` + "`" + `, ` + "`" + `created` + "`" + `, ` + "`" + `updated` + "`" + `. Default is ` + "`" + `id` + "`" + `.",
"name": "sort_by",
"in": "query"
},
@ -4006,7 +4006,7 @@ var doc = `{
},
{
"type": "string",
"description": "The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"description": "The name of the field to filter by. Allowed values are all task properties except ` + "`" + `labels` + "`" + `, ` + "`" + `list` + "`" + ` and ` + "`" + `namespace` + "`" + `. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"name": "filter_by",
"in": "query"
},
@ -4018,7 +4018,7 @@ var doc = `{
},
{
"type": "string",
"description": "The comparator to use for a filter. Available values are ` + "`" + `equals` + "`" + `, ` + "`" + `greater` + "`" + `, ` + "`" + `greater_equals` + "`" + `, ` + "`" + `less` + "`" + ` and ` + "`" + `less_equals` + "`" + `. Defaults to ` + "`" + `equals` + "`" + `",
"description": "The comparator to use for a filter. Available values are ` + "`" + `equals` + "`" + `, ` + "`" + `greater` + "`" + `, ` + "`" + `greater_equals` + "`" + `, ` + "`" + `less` + "`" + `, ` + "`" + `less_equals` + "`" + `, ` + "`" + `like` + "`" + ` and ` + "`" + `in` + "`" + `. ` + "`" + `in` + "`" + ` expects comma-separated values in ` + "`" + `filter_value` + "`" + `. Defaults to ` + "`" + `equals` + "`" + `",
"name": "filter_comparator",
"in": "query"
},
@ -4027,6 +4027,12 @@ var doc = `{
"description": "The concatinator to use for filters. Available values are ` + "`" + `and` + "`" + ` or ` + "`" + `or` + "`" + `. Defaults to ` + "`" + `or` + "`" + `.",
"name": "filter_concat",
"in": "query"
},
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to ` + "`" + `null` + "`" + `. Available values are ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `. Defaults to ` + "`" + `false` + "`" + `.",
"name": "filter_include_nulls",
"in": "query"
}
],
"responses": {

View file

@ -1875,7 +1875,7 @@
},
{
"type": "string",
"description": "The name of the field to filter by. Allowed values are all task properties except `labels`, `assignees`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"description": "The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"name": "filter_by",
"in": "query"
},
@ -1887,7 +1887,7 @@
},
{
"type": "string",
"description": "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals` and `like`. Defaults to `equals`",
"description": "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`",
"name": "filter_comparator",
"in": "query"
},
@ -3977,7 +3977,7 @@
},
{
"type": "string",
"description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.",
"description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.",
"name": "sort_by",
"in": "query"
},
@ -3989,7 +3989,7 @@
},
{
"type": "string",
"description": "The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"description": "The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.",
"name": "filter_by",
"in": "query"
},
@ -4001,7 +4001,7 @@
},
{
"type": "string",
"description": "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less` and `less_equals`. Defaults to `equals`",
"description": "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`",
"name": "filter_comparator",
"in": "query"
},
@ -4010,6 +4010,12 @@
"description": "The concatinator to use for filters. Available values are `and` or `or`. Defaults to `or`.",
"name": "filter_concat",
"in": "query"
},
{
"type": "string",
"description": "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`.",
"name": "filter_include_nulls",
"in": "query"
}
],
"responses": {

View file

@ -2443,7 +2443,7 @@ paths:
in: query
name: order_by
type: string
- description: The name of the field to filter by. Allowed values are all task properties except `labels`, `assignees`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.
- description: The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.
in: query
name: filter_by
type: string
@ -2451,7 +2451,7 @@ paths:
in: query
name: filter_value
type: string
- description: The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals` and `like`. Defaults to `equals`
- description: The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`
in: query
name: filter_comparator
type: string
@ -4459,7 +4459,7 @@ paths:
in: query
name: s
type: string
- description: The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.
- description: The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.
in: query
name: sort_by
type: string
@ -4467,7 +4467,7 @@ paths:
in: query
name: order_by
type: string
- description: The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.
- description: The name of the field to filter by. Allowed values are all task properties except `labels`, `list` and `namespace`. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match.
in: query
name: filter_by
type: string
@ -4475,7 +4475,7 @@ paths:
in: query
name: filter_value
type: string
- description: The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less` and `less_equals`. Defaults to `equals`
- description: The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less`, `less_equals`, `like` and `in`. `in` expects comma-separated values in `filter_value`. Defaults to `equals`
in: query
name: filter_comparator
type: string
@ -4483,6 +4483,10 @@ paths:
in: query
name: filter_concat
type: string
- description: If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`.
in: query
name: filter_include_nulls
type: string
produces:
- application/json
responses: