mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-06-01 19:01:37 +00:00
feat: support filter_include_nulls in project view configuration
This commit is contained in:
@@ -9,6 +9,7 @@ import {useLabelStore} from '@/stores/labels'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
import XButton from '@/components/input/Button.vue'
|
||||
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
|
||||
import FilterInputDocs from '@/components/input/filter/FilterInputDocs.vue'
|
||||
import FilterInput from '@/components/input/filter/FilterInput.vue'
|
||||
import FormField from '@/components/input/FormField.vue'
|
||||
@@ -58,6 +59,16 @@ onBeforeMount(() => {
|
||||
filter.filter = filter.s
|
||||
}
|
||||
|
||||
// AbstractModel.assignData() runs objectToCamelCase recursively on all
|
||||
// nested objects, which converts filter_include_nulls to filterIncludeNulls
|
||||
// inside the filter object. IFilters intentionally uses snake_case keys to
|
||||
// match the API query param format. We check both key forms here to handle
|
||||
// data coming from either the API response (camelCased by assignData) or
|
||||
// from a freshly constructed filter object (snake_case).
|
||||
filter.filter_include_nulls = filterInput.filter_include_nulls
|
||||
?? (filterInput as Record<string, unknown>).filterIncludeNulls as boolean
|
||||
?? false
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
@@ -76,16 +87,18 @@ onBeforeMount(() => {
|
||||
})
|
||||
|
||||
function save() {
|
||||
const transformFilterForApi = (filterQuery: string): IFilters => {
|
||||
const transformFilterForApi = (filterInput: IFilters): IFilters => {
|
||||
const filterString = transformFilterStringForApi(
|
||||
filterQuery,
|
||||
filterInput?.filter || '',
|
||||
labelTitle => labelStore.getLabelByExactTitle(labelTitle)?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
},
|
||||
)
|
||||
const filter: IFilters = {}
|
||||
const filter: IFilters = {
|
||||
filter_include_nulls: filterInput?.filter_include_nulls ?? false,
|
||||
}
|
||||
if (hasFilterQuery(filterString)) {
|
||||
filter.filter = filterString
|
||||
} else {
|
||||
@@ -97,10 +110,10 @@ function save() {
|
||||
|
||||
emit('update:modelValue', {
|
||||
...view.value,
|
||||
filter: transformFilterForApi(view.value?.filter?.filter || ''),
|
||||
filter: transformFilterForApi(view.value?.filter),
|
||||
bucketConfiguration: view.value?.bucketConfiguration.map(bc => ({
|
||||
title: bc.title,
|
||||
filter: transformFilterForApi(bc.filter?.filter || ''),
|
||||
filter: transformFilterForApi(bc.filter),
|
||||
})),
|
||||
})
|
||||
}
|
||||
@@ -172,10 +185,18 @@ function handleBubbleSave() {
|
||||
class="mbe-1"
|
||||
/>
|
||||
|
||||
<div class="is-size-7 mbe-3">
|
||||
<div class="is-size-7 mbe-2">
|
||||
<FilterInputDocs />
|
||||
</div>
|
||||
|
||||
<div class="field mbe-3">
|
||||
<FancyCheckbox
|
||||
v-model="view.filter.filter_include_nulls"
|
||||
>
|
||||
{{ $t('filters.attributes.includeNulls') }}
|
||||
</FancyCheckbox>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="view.viewKind === 'kanban'"
|
||||
class="field"
|
||||
@@ -245,16 +266,24 @@ function handleBubbleSave() {
|
||||
class="mbe-2"
|
||||
/>
|
||||
|
||||
<div class="is-size-7">
|
||||
<div class="is-size-7 mbe-2">
|
||||
<FilterInputDocs />
|
||||
</div>
|
||||
|
||||
<div class="field mbe-3">
|
||||
<FancyCheckbox
|
||||
v-model="view.bucketConfiguration[index].filter.filter_include_nulls"
|
||||
>
|
||||
{{ $t('filters.attributes.includeNulls') }}
|
||||
</FancyCheckbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-flex is-justify-content-end">
|
||||
<XButton
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
@click="() => view.bucketConfiguration.push({title: '', filter: {filter: ''}})"
|
||||
@click="() => view.bucketConfiguration.push({title: '', filter: {filter: '', filter_include_nulls: false}})"
|
||||
>
|
||||
{{ $t('project.kanban.addBucket') }}
|
||||
</XButton>
|
||||
|
||||
@@ -1007,3 +1007,11 @@
|
||||
updated: '2024-03-18 15:14:13'
|
||||
created: '2018-03-18 15:14:13'
|
||||
bucket_configuration_mode: 1
|
||||
- id: 161
|
||||
title: List with include nulls
|
||||
project_id: 1
|
||||
view_kind: 0
|
||||
position: 5
|
||||
filter: '{"filter":"start_date > ''2018-12-11T03:46:40+00:00'' || end_date < ''2018-12-13T11:20:01+00:00''","filter_include_nulls":true}'
|
||||
updated: '2024-03-18 15:14:13'
|
||||
created: '2018-03-18 15:14:13'
|
||||
|
||||
@@ -335,6 +335,10 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
||||
if view.Filter.Search != "" {
|
||||
search = view.Filter.Search
|
||||
}
|
||||
|
||||
if view.Filter.FilterIncludeNulls {
|
||||
tf.FilterIncludeNulls = true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(tf.Filter, taskPropertyBucketID) {
|
||||
|
||||
@@ -1007,6 +1007,39 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
// Tests that FilterIncludeNulls set on a view's saved filter config
|
||||
// is properly applied when loading tasks through that view.
|
||||
name: "range with nulls from view filter",
|
||||
fields: fields{
|
||||
ProjectViewID: 161,
|
||||
ProjectID: 1,
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task1, // has nil dates
|
||||
task2, // has nil dates
|
||||
task3, // has nil dates
|
||||
task4, // has nil dates
|
||||
task5, // has nil dates
|
||||
task6, // has nil dates
|
||||
task7, // matches start_date filter
|
||||
task8, // matches end_date filter
|
||||
task9, // matches both
|
||||
task10, // has nil dates
|
||||
task11, // has nil dates
|
||||
task12, // has nil dates
|
||||
task27, // has start_date, matches filter
|
||||
task28, // has dates, matches filter
|
||||
task29, // has nil dates
|
||||
task30, // has nil dates
|
||||
task31, // has nil dates
|
||||
task33, // has nil dates
|
||||
task47, // has nil dates
|
||||
task48, // has nil dates
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "favorited tasks",
|
||||
args: defaultArgs,
|
||||
|
||||
Reference in New Issue
Block a user