fix(views): enable search in bucket filters

This commit is contained in:
kolaente 2024-11-19 12:58:41 +01:00
parent 88873aec85
commit d7d39b6f1a
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
7 changed files with 159 additions and 46 deletions

View File

@ -3,7 +3,7 @@ import type {IProjectView} from '@/modelTypes/IProjectView'
import type {IFilter} from '@/modelTypes/ISavedFilter'
import XButton from '@/components/input/Button.vue'
import FilterInput from '@/components/project/partials/FilterInput.vue'
import {ref, onBeforeMount} from 'vue'
import {onBeforeMount, ref} from 'vue'
import {hasFilterQuery, transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects'
@ -29,27 +29,24 @@ const labelStore = useLabelStore()
const projectStore = useProjectStore()
onBeforeMount(() => {
const transform = (filterString: string) => transformFilterStringFromApi(
filterString,
labelId => labelStore.getLabelById(labelId)?.title || null,
projectId => projectStore.projects[projectId]?.title || null,
)
const filterString = transform(props.modelValue.filter.filter)
const transformFilterToString = (filter: IFilter): string => {
if (filter.s !== '') {
return filter.s
}
const filter: IFilter = {}
if (hasFilterQuery(filterString)) {
filter.filter = filterString
} else {
filter.s = filterString
return transformFilterStringFromApi(
filter.filter,
labelId => labelStore.getLabelById(labelId)?.title || null,
projectId => projectStore.projects[projectId]?.title || null,
)
}
const transformed = {
...props.modelValue,
filter,
filter: transformFilterToString(props.modelValue.filter),
bucketConfiguration: props.modelValue.bucketConfiguration.map(bc => ({
title: bc.title,
filter: transform(bc.filter),
filter: transformFilterToString(bc.filter),
})),
}
@ -59,30 +56,31 @@ onBeforeMount(() => {
})
function save() {
const transformFilter = (filterQuery: string) => transformFilterStringForApi(
filterQuery,
labelTitle => labelStore.getLabelByExactTitle(labelTitle)?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
return found?.id || null
},
)
const filterString = transformFilter(view.value?.filter?.filter)
const filter: IFilter = {}
if (hasFilterQuery(filterString)) {
filter.filter = filterString
} else {
filter.s = filterString
const transformFilterForApi = (filterQuery: string): IFilter => {
const filterString = transformFilterStringForApi(
filterQuery,
labelTitle => labelStore.getLabelByExactTitle(labelTitle)?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
return found?.id || null
},
)
const filter: IFilter = {}
if (hasFilterQuery(filterString)) {
filter.filter = filterString
} else {
filter.s = filterString
}
return filter
}
emit('update:modelValue', {
...view.value,
filter,
filter: transformFilterForApi(view.value?.filter || ''),
bucketConfiguration: view.value?.bucketConfiguration.map(bc => ({
title: bc.title,
filter: transformFilter(bc.filter),
filter: transformFilterForApi(bc.filter || ''),
})),
})
}
@ -160,7 +158,7 @@ function handleBubbleSave() {
</div>
<FilterInput
v-model="view.filter.filter"
v-model="view.filter"
:project-id="view.projectId"
:input-label="$t('project.views.filter')"
class="mb-1"
@ -250,7 +248,7 @@ function handleBubbleSave() {
<XButton
variant="secondary"
icon="plus"
@click="() => view.bucketConfiguration.push({title: '', filter: ''})"
@click="() => view.bucketConfiguration.push({title: '', filter: {filter: ''}})"
>
{{ $t('project.kanban.addBucket') }}
</XButton>

View File

@ -21,7 +21,7 @@ export type ProjectViewBucketConfigurationMode = typeof PROJECT_VIEW_BUCKET_CONF
export interface IProjectViewBucketConfiguration {
title: string
filter: string
filter: IFilters
}
export interface IProjectView extends IAbstract {

View File

@ -0,0 +1,95 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type projectView20241119115012BucketConfiguration struct {
Title string `json:"title"`
Filter string `json:"filter"`
}
type projectView20241119115012 struct {
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
BucketConfiguration []*projectView20241119115012BucketConfiguration `xorm:"json" json:"bucket_configuration"`
}
func (projectView20241119115012) TableName() string {
return "project_views"
}
type projectView20241119115012BucketConfigurationNew struct {
Title string `json:"title"`
Filter *taskCollection20241118123644 `json:"filter"`
}
type projectView20241119115012New struct {
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
BucketConfiguration []*projectView20241119115012BucketConfigurationNew `xorm:"json" json:"bucket_configuration"`
}
func (projectView20241119115012New) TableName() string {
return "project_views"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20241119115012",
Description: "change bucket filter format",
Migrate: func(tx *xorm.Engine) (err error) {
oldViews := []*projectView20241119115012{}
err = tx.Where("bucket_configuration_mode = 2").Find(&oldViews)
if err != nil {
return
}
err = tx.Sync(projectView20241119115012New{})
if err != nil {
return
}
for _, view := range oldViews {
newView := &projectView20241119115012New{
ID: view.ID,
}
for _, configuration := range view.BucketConfiguration {
newView.BucketConfiguration = append(newView.BucketConfiguration, &projectView20241119115012BucketConfigurationNew{
Title: configuration.Title,
Filter: &taskCollection20241118123644{
Filter: configuration.Filter,
},
})
}
_, err = tx.Where("id = ?", view.ID).Update(newView)
if err != nil {
return
}
}
return
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -230,7 +230,14 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, projects []*Pr
var bucketFilter = taskPropertyBucketID + " = " + strconv.FormatInt(id, 10)
if view.BucketConfigurationMode == BucketConfigurationModeFilter {
bucketFilter = "(" + view.BucketConfiguration[id].Filter + ")"
bucketFilter = ""
if view.BucketConfiguration[id].Filter.Filter != "" {
bucketFilter = "(" + view.BucketConfiguration[id].Filter.Filter + ")"
}
if view.BucketConfiguration[id].Filter.Search != "" {
opts.search = view.BucketConfiguration[id].Filter.Search
}
}
var filterString string

View File

@ -116,8 +116,8 @@ func (p *BucketConfigurationModeKind) UnmarshalJSON(bytes []byte) error {
}
type ProjectViewBucketConfiguration struct {
Title string `json:"title"`
Filter string `json:"filter"`
Title string `json:"title"`
Filter *TaskCollection `json:"filter"`
}
type ProjectView struct {
@ -281,6 +281,17 @@ func createProjectView(s *xorm.Session, p *ProjectView, a web.Auth, createBacklo
}
}
if p.BucketConfigurationMode == BucketConfigurationModeFilter {
for _, configuration := range p.BucketConfiguration {
if configuration.Filter != nil && configuration.Filter.Filter != "" {
_, err = getTaskFiltersFromFilterString(configuration.Filter.Filter, configuration.Filter.FilterTimezone)
if err != nil {
return
}
}
}
}
p.ID = 0
_, err = s.Insert(p)
if err != nil {

View File

@ -66,7 +66,7 @@ func TestSavedFilter_Create(t *testing.T) {
vals := map[string]interface{}{
"title": "'test'",
"description": "'Lorem Ipsum dolor sit amet'",
"filters": "'{\"sort_by\":null,\"order_by\":null,\"filter\":\"\",\"filter_include_nulls\":false}'",
"filters": `'{"s":"","sort_by":null,"order_by":null,"filter":"","filter_include_nulls":false}'`,
"owner_id": 1,
}
// Postgres can't compare json values directly, see https://dba.stackexchange.com/a/106290/210721

View File

@ -183,6 +183,10 @@ func getRelevantProjectsFromCollection(s *xorm.Session, a web.Auth, tf *TaskColl
}
func getFilterValueForBucketFilter(filter string, view *ProjectView) (newFilter string, err error) {
if view.BucketConfigurationMode != BucketConfigurationModeFilter {
return filter, nil
}
re := regexp.MustCompile(`bucket_id\s*=\s*(\d+)`)
match := re.FindStringSubmatch(filter)
@ -197,7 +201,7 @@ func getFilterValueForBucketFilter(filter string, view *ProjectView) (newFilter
for id, bucket := range view.BucketConfiguration {
if id == bucketID {
return re.ReplaceAllString(filter, `(`+bucket.Filter+`)`), nil
return re.ReplaceAllString(filter, `(`+bucket.Filter.Filter+`)`), nil
}
}
@ -304,11 +308,9 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
if strings.Contains(tf.Filter, taskPropertyBucketID) {
filteringForBucket = true
if view.BucketConfigurationMode == BucketConfigurationModeFilter {
tf.Filter, err = getFilterValueForBucketFilter(tf.Filter, view)
if err != nil {
return nil, 0, 0, err
}
tf.Filter, err = getFilterValueForBucketFilter(tf.Filter, view)
if err != nil {
return nil, 0, 0, err
}
}
}