1
0

fix(filter): correctly filter for buckets

This commit is contained in:
kolaente 2023-11-22 10:33:03 +01:00
parent ef1cc9720c
commit eebfee73d3
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
5 changed files with 57 additions and 22 deletions

View File

@ -1045,7 +1045,7 @@ func IsErrInvalidFilterExpression(err error) bool {
} }
func (err ErrInvalidFilterExpression) Error() string { func (err ErrInvalidFilterExpression) Error() string {
return fmt.Sprintf("Task filter expression is invalid [ExpressionError: %v]", err.ExpressionError) return fmt.Sprintf("Task filter expression '%s' is invalid [ExpressionError: %v]", err.Expression, err.ExpressionError)
} }
// ErrCodeInvalidFilterExpression holds the unique world-error code of this error // ErrCodeInvalidFilterExpression holds the unique world-error code of this error

View File

@ -17,6 +17,8 @@
package models package models
import ( import (
"strconv"
"strings"
"time" "time"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
@ -175,26 +177,35 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
opts.search = search opts.search = search
opts.filterConcat = filterConcatAnd opts.filterConcat = filterConcatAnd
var bucketFilterIndex int for _, filter := range opts.filters {
for i, filter := range opts.filters {
if filter.field == taskPropertyBucketID { if filter.field == taskPropertyBucketID {
bucketFilterIndex = i
// Limiting the map to the one filter we're looking for is the easiest way to ensure we only
// get tasks in this bucket
bucketID := filter.value.(int64)
bucket := bucketMap[bucketID]
bucketMap = make(map[int64]*Bucket, 1)
bucketMap[bucketID] = bucket
break break
} }
} }
if bucketFilterIndex == 0 { originalFilter := opts.filter
opts.filters = append(opts.filters, &taskFilter{
field: taskPropertyBucketID,
value: 0,
comparator: taskFilterComparatorEquals,
})
bucketFilterIndex = len(opts.filters) - 1
}
for id, bucket := range bucketMap { for id, bucket := range bucketMap {
opts.filters[bucketFilterIndex].value = id if !strings.Contains(originalFilter, "bucket_id") {
var filterString string
if originalFilter == "" {
filterString = "bucket_id = " + strconv.FormatInt(id, 10)
} else {
filterString = "(" + originalFilter + ") && bucket_id = " + strconv.FormatInt(id, 10)
}
opts.filters, err = getTaskFiltersFromFilterString(filterString)
if err != nil {
return
}
}
ts, _, total, err := getRawTasksForProjects(s, []*Project{{ID: bucket.ProjectID}}, auth, opts) ts, _, total, err := getRawTasksForProjects(s, []*Project{{ID: bucket.ProjectID}}, auth, opts)
if err != nil { if err != nil {

View File

@ -92,6 +92,30 @@ func TestBucket_ReadAll(t *testing.T) {
assert.Equal(t, int64(2), buckets[0].Tasks[0].ID) assert.Equal(t, int64(2), buckets[0].Tasks[0].ID)
assert.Equal(t, int64(33), buckets[0].Tasks[1].ID) assert.Equal(t, int64(33), buckets[0].Tasks[1].ID)
}) })
t.Run("filtered by bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
testuser := &user.User{ID: 1}
b := &Bucket{
ProjectID: 1,
TaskCollection: TaskCollection{
Filter: "title ~ 'task' && bucket_id = 2",
},
}
bucketsInterface, _, _, err := b.ReadAll(s, testuser, "", -1, 0)
assert.NoError(t, err)
buckets := bucketsInterface.([]*Bucket)
assert.Len(t, buckets, 3)
assert.Len(t, buckets[0].Tasks, 0)
assert.Len(t, buckets[1].Tasks, 3)
assert.Len(t, buckets[2].Tasks, 0)
assert.Equal(t, int64(3), buckets[1].Tasks[0].ID)
assert.Equal(t, int64(4), buckets[1].Tasks[1].ID)
assert.Equal(t, int64(5), buckets[1].Tasks[2].ID)
})
t.Run("accessed by link share", func(t *testing.T) { t.Run("accessed by link share", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()

View File

@ -105,7 +105,7 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskSearchOption
filter: tf.Filter, filter: tf.Filter,
} }
opts.filters, err = getTaskFiltersByCollections(tf) opts.filters, err = getTaskFiltersFromFilterString(tf.Filter)
return opts, err return opts, err
} }

View File

@ -144,29 +144,29 @@ func parseFilterFromExpression(f fexpr.ExprGroup) (filter *taskFilter, err error
return filter, nil return filter, nil
} }
func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err error) { func getTaskFiltersFromFilterString(filter string) (filters []*taskFilter, err error) {
if c.Filter == "" { if filter == "" {
return return
} }
c.Filter = strings.ReplaceAll(c.Filter, " in ", " ?= ") filter = strings.ReplaceAll(filter, " in ", " ?= ")
parsedFilter, err := fexpr.Parse(c.Filter) parsedFilter, err := fexpr.Parse(filter)
if err != nil { if err != nil {
return nil, &ErrInvalidFilterExpression{ return nil, &ErrInvalidFilterExpression{
Expression: c.Filter, Expression: filter,
ExpressionError: err, ExpressionError: err,
} }
} }
filters = make([]*taskFilter, 0, len(parsedFilter)) filters = make([]*taskFilter, 0, len(parsedFilter))
for _, f := range parsedFilter { for _, f := range parsedFilter {
filter, err := parseFilterFromExpression(f) parsedFilter, err := parseFilterFromExpression(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
filters = append(filters, filter) filters = append(filters, parsedFilter)
} }
return return