Skip to content

Commit

Permalink
Make dataset filter store
Browse files Browse the repository at this point in the history
The dataset filter is used e.g. by the table filter.
  • Loading branch information
gappc committed Jan 12, 2024
1 parent 9ca3684 commit c09771e
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

import { acceptHMRUpdate, defineStore, storeToRefs } from 'pinia';
import { Ref, computed } from 'vue';
import { MaybeRef, Ref, computed, toValue } from 'vue';
import { useRouter } from 'vue-router';
import { useDatasetBaseInfoStore } from '../../config/store/datasetBaseInfoStore';

Expand Down Expand Up @@ -36,21 +36,26 @@ export const useDatasetQueryStore = defineStore('datasetRoutingStore', () => {
>((prev, name) => ({ ...prev, [name]: buildQueryHandler(name) }), {});
});

const handle = (name: string) =>
computed({
get: () => queryHandlers.value[name]?.value,
const handle = (name: MaybeRef<string>) => {
const nameValue = toValue(name);
return computed({
get: () => queryHandlers.value[nameValue]?.value,
set: (value: string | undefined) => {
// Handle the case where the query handler is not defined, e.g. because
// the query parameter is not defined in the dataset config. Examples
// for this are the query parameters "searchfilter" or "rawfilter"
if (queryHandlers.value[name] == null) {
const query = { ...router.currentRoute.value.query, [name]: value };
if (queryHandlers.value[nameValue] == null) {
const query = {
...router.currentRoute.value.query,
[nameValue]: value,
};
router.push({ query });
} else {
queryHandlers.value[name].value = value;
queryHandlers.value[nameValue].value = value;
}
},
});
};

return { handle };
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
class="flex min-w-[20em] items-center gap-2 p-2"
:size="Size.xs"
:variant="Variant.ghost"
@click="removeFilter"
@click="removeFilterByPropertyPath(propertyPath)"
>
<IconDelete class="text-delete" /> Reset filter
</ButtonCustom>
Expand Down Expand Up @@ -50,7 +50,7 @@ const props = withDefaults(

const { title, propertyPath } = toRefs(props);

const { addFilter, removeFilter } = useTableFilterStore();
const { addFilter, removeFilterByPropertyPath } = useTableFilterStore();

const isFilterActive = useTableFilterStore().isFilterActive(propertyPath);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: NOI Techpark <[email protected]>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { DatasetDomain } from '../../../../config/types';
import { BaseFilter } from '../types';
import { buildMobilityFilterValues } from './mobilityFilterBuilder';
import { buildTourismFilterValues } from './tourismFilterBuilder';

export const buildFilterValues = (
datasetDomain: DatasetDomain | undefined,
updatedFilters: BaseFilter[]
): string[] => {
if (datasetDomain === 'tourism') {
return buildTourismFilterValues(updatedFilters);
}
if (datasetDomain === 'mobility') {
return buildMobilityFilterValues(updatedFilters);
}
console.error(
`Can not build filter values for unknown dataset domain "${datasetDomain}", returning empty list`
);
return [];
};

export const buildFilterValuesString = (
datasetDomain: DatasetDomain | undefined,
updatedFilters: BaseFilter[]
): string | undefined => {
const filterValues = buildFilterValues(datasetDomain, updatedFilters);
return filterValues.length > 0 ? `and(${filterValues.join(',')})` : undefined;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: NOI Techpark <[email protected]>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { BaseFilter } from '../types';

export const buildMobilityFilterValues = (updatedFilters: BaseFilter[]) =>
updatedFilters.reduce<string[]>((prev, curr) => {
if (curr.value === '' || curr.value == null) {
return prev;
}

const value = toMobilityFilterValue(curr);

if (value === '' || value == null) {
return prev;
}

switch (curr.operator) {
case 'eq':
return [...prev, `${curr.propertyPath}.eq.${value}`];
case 'neq':
return [...prev, `${curr.propertyPath}.neq.${value}`];
case 'gt':
return [...prev, `${curr.propertyPath}.gt.${value}`];
case 'gteq':
return [...prev, `${curr.propertyPath}.gteq.${value}`];
case 'lt':
return [...prev, `${curr.propertyPath}.lt.${value}`];
case 'lteq':
return [...prev, `${curr.propertyPath}.lteq.${value}`];
case 'in':
return [...prev, `${curr.propertyPath}.in.(${value})`];
case 'nin':
return [...prev, `${curr.propertyPath}.nin.(${value})`];
case 're':
return [...prev, `${curr.propertyPath}.re.${value}`];
case 'ire':
return [...prev, `${curr.propertyPath}.ire.${value}`];
case 'nre':
return [...prev, `${curr.propertyPath}.nre.${value}`];
case 'nire':
return [...prev, `${curr.propertyPath}.nire.${value}`];
case 'bbi':
return [...prev, `${curr.propertyPath}.bbi.(${value})`];
case 'bbc':
return [...prev, `${curr.propertyPath}.bbc.(${value})`];
default:
return prev;
}
}, []);

// We need to surround the filter value with double quotes only if it is a string.
// If its a string, we need to further check if it can be converted to a number
// or boolean. If so, no double quotes must be added.
const toMobilityFilterValue = ({ operator, value }: BaseFilter) => {
if (typeof value !== 'string') {
return value;
}

const number = Number(value);
if (!isNaN(number)) {
return number;
}

const lowerCasedValue = value.toLowerCase();
if (lowerCasedValue === 'true' || lowerCasedValue === 'false') {
return value;
}

if (operator === 'bbi' || operator === 'bbc') {
return `${value}`;
}

return `"${value}"`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: NOI Techpark <[email protected]>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { BaseFilter } from '../types';

export const buildTourismFilterValues = (updatedFilters: BaseFilter[]) =>
updatedFilters.reduce<string[]>((prev, curr) => {
if (
curr.operator !== 'isnull' &&
curr.operator !== 'isnotnull' &&
(curr.value === '' || curr.value == null)
) {
return prev;
}

switch (curr.operator) {
case 'eq':
return [...prev, `eq(${curr.propertyPath},'${curr.value}')`];
case 'ne':
return [...prev, `ne(${curr.propertyPath},'${curr.value}')`];
case 'gt':
return [...prev, `gt(${curr.propertyPath},'${curr.value}')`];
case 'ge':
return [...prev, `ge(${curr.propertyPath},'${curr.value}')`];
case 'lt':
return [...prev, `lt(${curr.propertyPath},'${curr.value}')`];
case 'le':
return [...prev, `le(${curr.propertyPath},'${curr.value}')`];
case 'isnull':
return [...prev, `isnull(${curr.propertyPath})`];
case 'isnotnull':
return [...prev, `isnotnull(${curr.propertyPath})`];
case 'in':
return [...prev, `in(${curr.propertyPath}.[*],'${curr.value}')`];
case 'nin':
return [...prev, `nin(${curr.propertyPath}.[*],'${curr.value}')`];
case 'like':
return [...prev, `like(${curr.propertyPath},'${curr.value}')`];
case 'likein':
return [...prev, `likein(${curr.propertyPath}.[*],'${curr.value}')`];
default:
return prev;
}
}, []);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: NOI Techpark <[email protected]>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { acceptHMRUpdate, defineStore, storeToRefs } from 'pinia';
import { computed } from 'vue';
import { useDatasetBaseInfoStore } from '../../../config/store/datasetBaseInfoStore';
import { useDatasetQueryStore } from '../../../location/store/datasetQueryStore';
import { buildFilterValuesString } from './builder/filterBuilder';
import { parseFilterWithRegex } from './parser/filterParser';
import { BaseFilter } from './types';

export const useDatasetFilterStore = defineStore('datasetFilterStore', () => {
const { datasetDomain } = storeToRefs(useDatasetBaseInfoStore());

const urlFilterParamName = computed(() => {
if (datasetDomain.value === 'tourism') {
return 'rawfilter';
}
if (datasetDomain.value === 'mobility') {
return 'where';
}
return '';
});

const urlFilter = useDatasetQueryStore().handle(urlFilterParamName);

const datasetFilters = computed<BaseFilter[]>({
get: () => parseFilterWithRegex(datasetDomain.value, urlFilter.value),
set: (updatedFilters) =>
(urlFilter.value = buildFilterValuesString(
datasetDomain.value,
updatedFilters
)),
});

return { datasetFilters };
});

// Add support for hot-module-reload
if (import.meta.hot) {
import.meta.hot.accept(
acceptHMRUpdate(useDatasetFilterStore, import.meta.hot)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: NOI Techpark <[email protected]>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { DatasetDomain } from '../../../../config/types';
import { BaseFilter } from '../types';
import { mobilityParseFilterWithRegex } from './mobilityFilterParserWithRegex';
import { tourismParseFilterWithRegex } from './tourismFilterParserWithRegex';

export const parseFilterWithRegex = (
datasetDomain: DatasetDomain | undefined,
filterString?: string
): BaseFilter[] => {
if (datasetDomain === 'tourism') {
return tourismParseFilterWithRegex(filterString);
}
if (datasetDomain === 'mobility') {
return mobilityParseFilterWithRegex(filterString);
}
console.error(
`Can not parse filters for unknown dataset domain "${datasetDomain}", returning empty list`
);
return [];
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import {
FilterOperator,
Rawfilter,
RawfilterParser,
BaseFilter,
StringFilterParser,
isFilterOperator,
} from '../types';

Expand Down Expand Up @@ -69,14 +69,16 @@ import {
const filterRegex =
/(?<propertyPath>\w+)\.(?<operator>eq|neq|gt|lt|gteq|lteq|re|ire|nre|nire|in|nin|bbi|bbc)\.(?<value>true|false|\d+|"\w*"|\([\w.,]*\))/g;

export const mobilityParseFilterWithRegex: RawfilterParser = (rawfilter) => {
// If rawfilter is undefined, return an empty array.
if (rawfilter == undefined) {
export const mobilityParseFilterWithRegex: StringFilterParser = (
filterString
) => {
// If filterString is undefined, return an empty array.
if (filterString == undefined) {
return [];
}

// If rawfilter does not match the filterRegex, return an empty array.
const matches = rawfilter.matchAll(filterRegex);
// If filterString does not match the filterRegex, return an empty array.
const matches = filterString.matchAll(filterRegex);
if (matches == null) {
return [];
}
Expand All @@ -85,7 +87,7 @@ export const mobilityParseFilterWithRegex: RawfilterParser = (rawfilter) => {
.filter(
(g) => g != null && g.propertyPath != null && isFilterOperator(g.operator)
)
.map<Rawfilter>((g) => {
.map<BaseFilter>((g) => {
const propertyPath = convertToPropertyPath(g!.propertyPath);
const operator = g!.operator as FilterOperator;
const value = convertToValue(g!.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import {
FilterOperator,
Rawfilter,
RawfilterParser,
BaseFilter,
StringFilterParser,
isFilterOperator,
} from '../types';

Expand Down Expand Up @@ -65,14 +65,16 @@ import {
const filterRegex =
/(?<operator>eq|ne|gt|lt|ge|le|isnull|isnotnull|in|nin|like|likein)\((?<propertyPath>\w+(\.(\w+|\[(\*|\d+)?\]))*)(,\s*(?<value>true|false|\d+|\[\]|'(?:[^']|'')*'|"(?:[^"]|"")*"))?\)/g;

export const tourismParseFilterWithRegex: RawfilterParser = (rawfilter) => {
// If rawfilter is undefined, return an empty array.
if (rawfilter == undefined) {
export const tourismParseFilterWithRegex: StringFilterParser = (
filterString
) => {
// If filterString is undefined, return an empty array.
if (filterString == undefined) {
return [];
}

// If rawfilter does not match the filterRegex, return an empty array.
const matches = rawfilter.matchAll(filterRegex);
// If filterString does not match the filterRegex, return an empty array.
const matches = filterString.matchAll(filterRegex);
if (matches == null) {
return [];
}
Expand All @@ -81,7 +83,7 @@ export const tourismParseFilterWithRegex: RawfilterParser = (rawfilter) => {
.filter(
(g) => g != null && g.propertyPath != null && isFilterOperator(g.operator)
)
.map<Rawfilter>((g) => {
.map<BaseFilter>((g) => {
const propertyPath = convertToPropertyPath(g!.propertyPath);
const operator = g!.operator as FilterOperator;
const value = convertToValue(g!.value);
Expand Down
Loading

0 comments on commit c09771e

Please sign in to comment.