Add task filter for reminders (#745)
Update swagger docs about reminders Fix filter concat for reminders Add task filter for reminders Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/745 Co-Authored-By: konrad <konrad@kola-entertainments.de> Co-Committed-By: konrad <konrad@kola-entertainments.de>
This commit is contained in:
parent
9508d0faee
commit
92bcce3f7c
7 changed files with 91 additions and 45 deletions
|
@ -91,7 +91,7 @@ func validateTaskField(fieldName string) error {
|
||||||
// @Param s query string false "Search tasks by task text."
|
// @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 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 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`, `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_value query string false "The value to filter for."
|
// @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` and `like`. 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_concat query string false "The concatinator to use for filters. Available values are `and` or `or`. Defaults to `or`."
|
||||||
|
|
|
@ -163,6 +163,14 @@ func getNativeValueForTaskField(fieldName, value string) (nativeValue interface{
|
||||||
nativeValue, err = time.Parse(time.RFC3339, value)
|
nativeValue, err = time.Parse(time.RFC3339, value)
|
||||||
nativeValue = nativeValue.(time.Time).In(config.GetTimeZone())
|
nativeValue = nativeValue.(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())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
default:
|
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(), fieldName, value))
|
||||||
}
|
}
|
||||||
|
|
|
@ -879,6 +879,19 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "filtered reminders",
|
||||||
|
fields: fields{
|
||||||
|
FilterBy: []string{"reminders", "reminders"},
|
||||||
|
FilterValue: []string{"2018-10-01T00:00:00+00:00", "2018-12-10T00:00:00+00:00"},
|
||||||
|
FilterComparator: []string{"greater", "less"},
|
||||||
|
},
|
||||||
|
args: defaultArgs,
|
||||||
|
want: []*Task{
|
||||||
|
task27,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -156,6 +156,37 @@ func (t *Task) ReadAll(a web.Auth, search string, page int, perPage int) (result
|
||||||
return nil, 0, 0, nil
|
return nil, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err error) {
|
||||||
|
switch f.comparator {
|
||||||
|
case taskFilterComparatorEquals:
|
||||||
|
cond = &builder.Eq{f.field: f.value}
|
||||||
|
case taskFilterComparatorNotEquals:
|
||||||
|
cond = &builder.Neq{f.field: f.value}
|
||||||
|
case taskFilterComparatorGreater:
|
||||||
|
cond = &builder.Gt{f.field: f.value}
|
||||||
|
case taskFilterComparatorGreateEquals:
|
||||||
|
cond = &builder.Gte{f.field: f.value}
|
||||||
|
case taskFilterComparatorLess:
|
||||||
|
cond = &builder.Lt{f.field: f.value}
|
||||||
|
case taskFilterComparatorLessEquals:
|
||||||
|
cond = &builder.Lte{f.field: f.value}
|
||||||
|
case taskFilterComparatorLike:
|
||||||
|
val, is := f.value.(string)
|
||||||
|
if !is {
|
||||||
|
return nil, ErrInvalidTaskFilterValue{Field: f.field, Value: f.value}
|
||||||
|
}
|
||||||
|
cond = &builder.Like{f.field, "%" + val + "%"}
|
||||||
|
case taskFilterComparatorInvalid:
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
if includeNulls {
|
||||||
|
cond = builder.Or(cond, &builder.IsNull{f.field})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
|
func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
|
||||||
|
|
||||||
|
@ -215,52 +246,27 @@ func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reminder filters need a special treatment since they are in a separate database
|
||||||
|
reminderFilters := []builder.Cond{}
|
||||||
|
|
||||||
var filters = make([]builder.Cond, 0, len(opts.filters))
|
var filters = make([]builder.Cond, 0, len(opts.filters))
|
||||||
// To still find tasks with nil values, we exclude 0s when comparing with >/< values.
|
// To still find tasks with nil values, we exclude 0s when comparing with >/< values.
|
||||||
for _, f := range opts.filters {
|
for _, f := range opts.filters {
|
||||||
switch f.comparator {
|
if f.field == "reminders" {
|
||||||
case taskFilterComparatorEquals:
|
f.field = "reminder" // This is the name in the db
|
||||||
filters = append(filters, &builder.Eq{f.field: f.value})
|
filter, err := getFilterCond(f, opts.filterIncludeNulls)
|
||||||
case taskFilterComparatorNotEquals:
|
if err != nil {
|
||||||
filters = append(filters, &builder.Neq{f.field: f.value})
|
return nil, 0, 0, err
|
||||||
case taskFilterComparatorGreater:
|
|
||||||
if opts.filterIncludeNulls {
|
|
||||||
filters = append(filters, builder.Or(&builder.Gt{f.field: f.value}, &builder.IsNull{f.field}))
|
|
||||||
} else {
|
|
||||||
filters = append(filters, &builder.Gt{f.field: f.value})
|
|
||||||
}
|
}
|
||||||
case taskFilterComparatorGreateEquals:
|
reminderFilters = append(reminderFilters, filter)
|
||||||
if opts.filterIncludeNulls {
|
continue
|
||||||
filters = append(filters, builder.Or(&builder.Gte{f.field: f.value}, &builder.IsNull{f.field}))
|
|
||||||
} else {
|
|
||||||
filters = append(filters, &builder.Gte{f.field: f.value})
|
|
||||||
}
|
|
||||||
case taskFilterComparatorLess:
|
|
||||||
if opts.filterIncludeNulls {
|
|
||||||
filters = append(filters, builder.Or(&builder.Lt{f.field: f.value}, &builder.IsNull{f.field}))
|
|
||||||
} else {
|
|
||||||
filters = append(filters, &builder.Lt{f.field: f.value})
|
|
||||||
}
|
|
||||||
case taskFilterComparatorLessEquals:
|
|
||||||
if opts.filterIncludeNulls {
|
|
||||||
filters = append(filters, builder.Or(&builder.Lte{f.field: f.value}, &builder.IsNull{f.field}))
|
|
||||||
} else {
|
|
||||||
filters = append(filters, &builder.Lte{f.field: f.value})
|
|
||||||
}
|
|
||||||
case taskFilterComparatorLike:
|
|
||||||
val, is := f.value.(string)
|
|
||||||
if !is {
|
|
||||||
return nil, 0, 0, ErrInvalidTaskFilterValue{Field: f.field, Value: f.value}
|
|
||||||
}
|
|
||||||
c := &builder.Like{f.field, "%" + val + "%"}
|
|
||||||
if opts.filterIncludeNulls {
|
|
||||||
filters = append(filters, builder.Or(c, &builder.IsNull{f.field}))
|
|
||||||
} else {
|
|
||||||
filters = append(filters, c)
|
|
||||||
}
|
|
||||||
case taskFilterComparatorInvalid:
|
|
||||||
// Nothing to do
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter, err := getFilterCond(f, opts.filterIncludeNulls)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
filters = append(filters, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then return all tasks for that lists
|
// Then return all tasks for that lists
|
||||||
|
@ -308,6 +314,25 @@ func getRawTasksForLists(lists []*List, a web.Auth, opts *taskOptions) (tasks []
|
||||||
listCond = builder.Or(listIDCond, builder.And(builder.Eq{"is_favorite": true}, builder.In("list_id", userListIDs)))
|
listCond = builder.Or(listIDCond, builder.And(builder.Eq{"is_favorite": true}, builder.In("list_id", userListIDs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(reminderFilters) > 0 {
|
||||||
|
var filtercond builder.Cond
|
||||||
|
if opts.filterConcat == filterConcatOr {
|
||||||
|
filtercond = builder.Or(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)
|
||||||
|
}
|
||||||
|
|
||||||
query = query.Where(listCond)
|
query = query.Where(listCond)
|
||||||
queryCount = queryCount.Where(listCond)
|
queryCount = queryCount.Where(listCond)
|
||||||
|
|
||||||
|
|
|
@ -1892,7 +1892,7 @@ var doc = `{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"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` + "`" + `, ` + "`" + `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.",
|
||||||
"name": "filter_by",
|
"name": "filter_by",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1875,7 +1875,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"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`, `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.",
|
||||||
"name": "filter_by",
|
"name": "filter_by",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2443,7 +2443,7 @@ paths:
|
||||||
in: query
|
in: query
|
||||||
name: order_by
|
name: order_by
|
||||||
type: string
|
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`, `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.
|
||||||
in: query
|
in: query
|
||||||
name: filter_by
|
name: filter_by
|
||||||
type: string
|
type: string
|
||||||
|
|
Loading…
Reference in a new issue