diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go index ab87b3902..7e7da7836 100644 --- a/pkg/models/task_collection_filter.go +++ b/pkg/models/task_collection_filter.go @@ -55,6 +55,7 @@ type taskFilter struct { value interface{} // Needs to be an interface to be able to hold the field's native value comparator taskFilterComparator isNumeric bool + join taskFilterConcatinator } func parseTimeFromUserInput(timeString string) (value time.Time, err error) { @@ -103,11 +104,11 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err c.FilterComparator = append(c.FilterComparator, c.FilterComparatorArr...) } - if c.FilterConcat != "" && c.FilterConcat != filterConcatAnd && c.FilterConcat != filterConcatOr { - return nil, ErrInvalidTaskFilterConcatinator{ - Concatinator: taskFilterConcatinator(c.FilterConcat), - } - } + //if c.FilterConcat != "" && c.FilterConcat != filterConcatAnd && c.FilterConcat != filterConcatOr { + // return nil, ErrInvalidTaskFilterConcatinator{ + // Concatinator: taskFilterConcatinator(c.FilterConcat), + // } + //} parsedFilter, err := fexpr.Parse(c.Filter) if err != nil { @@ -115,18 +116,21 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err } filters = make([]*taskFilter, 0, len(parsedFilter)) - for i, f := range parsedFilter { + for _, f := range parsedFilter { - filter := &taskFilter{} + filter := &taskFilter{ + join: filterConcatAnd, + } + if f.Join == fexpr.JoinOr { + filter.join = filterConcatOr + } + + var value string switch v := f.Item.(type) { case fexpr.Expr: filter.field = v.Left.Literal - filter.comparator = v.Op - filter.value = v.Right.Literal // TODO: nesting - } - - if len(c.FilterComparator) > i { - filter.comparator, err = getFilterComparatorFromString(c.FilterComparator[i]) + value = v.Right.Literal // TODO: nesting + filter.comparator, err = getFilterComparatorFromOp(v.Op) if err != nil { return } @@ -139,13 +143,11 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err // Cast the field value to its native type var reflectValue *reflect.StructField - if len(c.FilterValue) > i { - reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, c.FilterValue[i]) - if err != nil { - return nil, ErrInvalidTaskFilterValue{ - Value: filter.field, - Field: c.FilterValue[i], - } + reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, value) + if err != nil { + return nil, ErrInvalidTaskFilterValue{ + Value: filter.field, + Field: value, } } if reflectValue != nil { @@ -200,6 +202,29 @@ func getFilterComparatorFromString(comparator string) (taskFilterComparator, err } } +func getFilterComparatorFromOp(op fexpr.SignOp) (taskFilterComparator, error) { + switch op { + case fexpr.SignEq: + return taskFilterComparatorEquals, nil + case fexpr.SignGt: + return taskFilterComparatorGreater, nil + case fexpr.SignGte: + return taskFilterComparatorGreateEquals, nil + case fexpr.SignLt: + return taskFilterComparatorLess, nil + case fexpr.SignLte: + return taskFilterComparatorLessEquals, nil + case fexpr.SignNeq: + return taskFilterComparatorNotEquals, nil + case fexpr.SignLike: + return taskFilterComparatorLike, nil + case "in": + return taskFilterComparatorIn, nil + default: + return taskFilterComparatorInvalid, ErrInvalidTaskFilterComparator{Comparator: taskFilterComparator(op)} + } +} + func getValueForField(field reflect.StructField, rawValue string) (value interface{}, err error) { switch field.Type.Kind() { case reflect.Int64: diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 77830979a..52d9117c8 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -794,10 +794,10 @@ func TestTaskCollection_ReadAll(t *testing.T) { { name: "ReadAll Tasks with range", fields: fields{ - FilterBy: []string{"start_date", "end_date"}, - FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"}, - FilterComparator: []string{"greater", "less"}, - Filter: "start_date>'2018-12-11T03:46:40+00:00' || end_date<'2018-12-13T11:20:01+00:00'", + //FilterBy: []string{"start_date", "end_date"}, + //FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"}, + //FilterComparator: []string{"greater", "less"}, + Filter: "start_date > '2018-12-11T03:46:40+00:00' || end_date < '2018-12-13T11:20:01+00:00'", }, args: defaultArgs, want: []*Task{ diff --git a/pkg/models/task_search.go b/pkg/models/task_search.go index 8a3601f3f..f1a00f4e4 100644 --- a/pkg/models/task_search.go +++ b/pkg/models/task_search.go @@ -84,12 +84,6 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo return nil, 0, err } - // Some filters need a special treatment since they are in a separate table - reminderFilters := []builder.Cond{} - assigneeFilters := []builder.Cond{} - labelFilters := []builder.Cond{} - projectFilters := []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. for _, f := range opts.filters { @@ -104,7 +98,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo if err != nil { return nil, totalCount, err } - reminderFilters = append(reminderFilters, filter) + filters = append(filters, getFilterCondForSeparateTable("task_reminders", filter)) continue } @@ -122,7 +116,13 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo if err != nil { return nil, totalCount, err } - assigneeFilters = append(assigneeFilters, filter) + + assigneeFilter := builder.In("user_id", + builder.Select("id"). + From("users"). + Where(filter), + ) + filters = append(filters, getFilterCondForSeparateTable("task_assignees", assigneeFilter)) continue } @@ -137,7 +137,8 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo if err != nil { return nil, totalCount, err } - labelFilters = append(labelFilters, filter) + + filters = append(filters, getFilterCondForSeparateTable("label_tasks", filter)) continue } @@ -152,7 +153,15 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo if err != nil { return nil, totalCount, err } - projectFilters = append(projectFilters, filter) + + cond := builder.In( + "project_id", + builder. + Select("id"). + From("projects"). + Where(filter), + ) + filters = append(filters, cond) continue } @@ -199,50 +208,17 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo favoritesCond = builder.In("id", favCond) } - if len(reminderFilters) > 0 { - filters = append(filters, getFilterCondForSeparateTable("task_reminders", opts.filterConcat, reminderFilters)) - } - - if len(assigneeFilters) > 0 { - assigneeFilter := []builder.Cond{ - builder.In("user_id", - builder.Select("id"). - From("users"). - Where(builder.Or(assigneeFilters...)), - )} - filters = append(filters, getFilterCondForSeparateTable("task_assignees", opts.filterConcat, assigneeFilter)) - } - - if len(labelFilters) > 0 { - filters = append(filters, getFilterCondForSeparateTable("label_tasks", opts.filterConcat, labelFilters)) - } - - if len(projectFilters) > 0 { - var filtercond builder.Cond - if opts.filterConcat == filterConcatOr { - filtercond = builder.Or(projectFilters...) - } - if opts.filterConcat == filterConcatAnd { - filtercond = builder.And(projectFilters...) - } - - cond := builder.In( - "project_id", - builder. - Select("id"). - From("projects"). - Where(filtercond), - ) - filters = append(filters, cond) - } - var filterCond builder.Cond if len(filters) > 0 { - if opts.filterConcat == filterConcatOr { - filterCond = builder.Or(filters...) - } - if opts.filterConcat == filterConcatAnd { - filterCond = builder.And(filters...) + for i, f := range filters { + if len(filters) > i+1 { + switch opts.filters[i].join { + case filterConcatOr: + filterCond = builder.Or(filterCond, f, filters[i+1]) + case filterConcatAnd: + filterCond = builder.And(filterCond, f, filters[i+1]) + } + } } } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 7b0f2a36c..613e41246 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -162,16 +162,17 @@ func (t *Task) GetFrontendURL() string { type taskFilterConcatinator string const ( - filterConcatAnd = "and" - filterConcatOr = "or" + filterConcatAnd taskFilterConcatinator = "and" + filterConcatOr taskFilterConcatinator = "or" ) type taskSearchOptions struct { - search string - page int - perPage int - sortby []*sortParam - filters []*taskFilter + search string + page int + perPage int + sortby []*sortParam + filters []*taskFilter + // deprecated: concat should live in filters directly filterConcat taskFilterConcatinator filterIncludeNulls bool filter string @@ -239,21 +240,13 @@ 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...) - } - +func getFilterCondForSeparateTable(table string, cond builder.Cond) builder.Cond { return builder.In( "id", builder. Select("task_id"). From(table). - Where(filtercond), + Where(cond), ) }