feat(filters): basic text filter works now
This commit is contained in:
parent
8d2f6c8567
commit
f470c0c297
|
@ -55,6 +55,7 @@ type taskFilter struct {
|
||||||
value interface{} // Needs to be an interface to be able to hold the field's native value
|
value interface{} // Needs to be an interface to be able to hold the field's native value
|
||||||
comparator taskFilterComparator
|
comparator taskFilterComparator
|
||||||
isNumeric bool
|
isNumeric bool
|
||||||
|
join taskFilterConcatinator
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTimeFromUserInput(timeString string) (value time.Time, err error) {
|
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...)
|
c.FilterComparator = append(c.FilterComparator, c.FilterComparatorArr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.FilterConcat != "" && c.FilterConcat != filterConcatAnd && c.FilterConcat != filterConcatOr {
|
//if c.FilterConcat != "" && c.FilterConcat != filterConcatAnd && c.FilterConcat != filterConcatOr {
|
||||||
return nil, ErrInvalidTaskFilterConcatinator{
|
// return nil, ErrInvalidTaskFilterConcatinator{
|
||||||
Concatinator: taskFilterConcatinator(c.FilterConcat),
|
// Concatinator: taskFilterConcatinator(c.FilterConcat),
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
parsedFilter, err := fexpr.Parse(c.Filter)
|
parsedFilter, err := fexpr.Parse(c.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,18 +116,21 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filters = make([]*taskFilter, 0, len(parsedFilter))
|
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) {
|
switch v := f.Item.(type) {
|
||||||
case fexpr.Expr:
|
case fexpr.Expr:
|
||||||
filter.field = v.Left.Literal
|
filter.field = v.Left.Literal
|
||||||
filter.comparator = v.Op
|
value = v.Right.Literal // TODO: nesting
|
||||||
filter.value = v.Right.Literal // TODO: nesting
|
filter.comparator, err = getFilterComparatorFromOp(v.Op)
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.FilterComparator) > i {
|
|
||||||
filter.comparator, err = getFilterComparatorFromString(c.FilterComparator[i])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -139,13 +143,11 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err
|
||||||
|
|
||||||
// Cast the field value to its native type
|
// Cast the field value to its native type
|
||||||
var reflectValue *reflect.StructField
|
var reflectValue *reflect.StructField
|
||||||
if len(c.FilterValue) > i {
|
reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, value)
|
||||||
reflectValue, filter.value, err = getNativeValueForTaskField(filter.field, filter.comparator, c.FilterValue[i])
|
if err != nil {
|
||||||
if err != nil {
|
return nil, ErrInvalidTaskFilterValue{
|
||||||
return nil, ErrInvalidTaskFilterValue{
|
Value: filter.field,
|
||||||
Value: filter.field,
|
Field: value,
|
||||||
Field: c.FilterValue[i],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reflectValue != nil {
|
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) {
|
func getValueForField(field reflect.StructField, rawValue string) (value interface{}, err error) {
|
||||||
switch field.Type.Kind() {
|
switch field.Type.Kind() {
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
|
|
|
@ -794,10 +794,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "ReadAll Tasks with range",
|
name: "ReadAll Tasks with range",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
FilterBy: []string{"start_date", "end_date"},
|
//FilterBy: []string{"start_date", "end_date"},
|
||||||
FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"},
|
//FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"},
|
||||||
FilterComparator: []string{"greater", "less"},
|
//FilterComparator: []string{"greater", "less"},
|
||||||
Filter: "start_date>'2018-12-11T03:46:40+00:00' || end_date<'2018-12-13T11:20:01+00:00'",
|
Filter: "start_date > '2018-12-11T03:46:40+00:00' || end_date < '2018-12-13T11:20:01+00:00'",
|
||||||
},
|
},
|
||||||
args: defaultArgs,
|
args: defaultArgs,
|
||||||
want: []*Task{
|
want: []*Task{
|
||||||
|
|
|
@ -84,12 +84,6 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
return nil, 0, err
|
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))
|
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 {
|
||||||
|
@ -104,7 +98,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, totalCount, err
|
return nil, totalCount, err
|
||||||
}
|
}
|
||||||
reminderFilters = append(reminderFilters, filter)
|
filters = append(filters, getFilterCondForSeparateTable("task_reminders", filter))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +116,13 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, totalCount, err
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,8 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, totalCount, err
|
return nil, totalCount, err
|
||||||
}
|
}
|
||||||
labelFilters = append(labelFilters, filter)
|
|
||||||
|
filters = append(filters, getFilterCondForSeparateTable("label_tasks", filter))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +153,15 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, totalCount, err
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,50 +208,17 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
|
||||||
favoritesCond = builder.In("id", favCond)
|
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
|
var filterCond builder.Cond
|
||||||
if len(filters) > 0 {
|
if len(filters) > 0 {
|
||||||
if opts.filterConcat == filterConcatOr {
|
for i, f := range filters {
|
||||||
filterCond = builder.Or(filters...)
|
if len(filters) > i+1 {
|
||||||
}
|
switch opts.filters[i].join {
|
||||||
if opts.filterConcat == filterConcatAnd {
|
case filterConcatOr:
|
||||||
filterCond = builder.And(filters...)
|
filterCond = builder.Or(filterCond, f, filters[i+1])
|
||||||
|
case filterConcatAnd:
|
||||||
|
filterCond = builder.And(filterCond, f, filters[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,16 +162,17 @@ func (t *Task) GetFrontendURL() string {
|
||||||
type taskFilterConcatinator string
|
type taskFilterConcatinator string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
filterConcatAnd = "and"
|
filterConcatAnd taskFilterConcatinator = "and"
|
||||||
filterConcatOr = "or"
|
filterConcatOr taskFilterConcatinator = "or"
|
||||||
)
|
)
|
||||||
|
|
||||||
type taskSearchOptions struct {
|
type taskSearchOptions struct {
|
||||||
search string
|
search string
|
||||||
page int
|
page int
|
||||||
perPage int
|
perPage int
|
||||||
sortby []*sortParam
|
sortby []*sortParam
|
||||||
filters []*taskFilter
|
filters []*taskFilter
|
||||||
|
// deprecated: concat should live in filters directly
|
||||||
filterConcat taskFilterConcatinator
|
filterConcat taskFilterConcatinator
|
||||||
filterIncludeNulls bool
|
filterIncludeNulls bool
|
||||||
filter string
|
filter string
|
||||||
|
@ -239,21 +240,13 @@ func getFilterCond(f *taskFilter, includeNulls bool) (cond builder.Cond, err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFilterCondForSeparateTable(table string, concat taskFilterConcatinator, conds []builder.Cond) builder.Cond {
|
func getFilterCondForSeparateTable(table string, cond 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(
|
return builder.In(
|
||||||
"id",
|
"id",
|
||||||
builder.
|
builder.
|
||||||
Select("task_id").
|
Select("task_id").
|
||||||
From(table).
|
From(table).
|
||||||
Where(filtercond),
|
Where(cond),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue