Skip to content

Commit

Permalink
fix: allow multiple filters to be selected for a query layer
Browse files Browse the repository at this point in the history
Fixes #705
  • Loading branch information
stdavis committed Nov 22, 2024
1 parent bc2f772 commit 2f13606
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 74 deletions.
50 changes: 6 additions & 44 deletions src/components/search-wizard/QueryLayer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,6 @@ import Button from '../../utah-design-system/Button';
* @typedef {import('../../contexts/SearchMachineProvider').LayerFilterValue} LayerFilterValue
*/

/**
* @param {FieldFilterConfig
* | import('../../../functions/common/config').CheckboxRadioQueriesFilterConfig
* | import('../../../functions/common/config').DateFilterConfig} filterConfig
* @param {LayerFilterValue[]} filterValues
* @returns {number}
*/
function getFilterValueIndex(filterConfig, filterValues) {
if (!filterValues) {
return -1;
}

const index = filterValues.findIndex((filter) => {
const typesMatch = filter.type === filterConfig.type;
if (/** @type {FieldFilterConfig} */ (filterConfig).field) {
return (
typesMatch &&
filter.field === /** @type {FieldFilterConfig} */ (filterConfig).field
);
}

return typesMatch;
});

return index;
}

/**
* @param {Object} props
* @param {import('../../../functions/common/config').QueryLayerConfig} props.config
Expand Down Expand Up @@ -119,29 +92,18 @@ export default function QueryLayer({
*/ filterConfig,
/** @type {number} */ index,
) => {
const filterValueIndex = getFilterValueIndex(
filterConfig,
filterValues,
);
return (
<Fragment key={index}>
{index > 0 && <hr />}
<SpecialFilter
config={filterConfig}
value={
filterValues ? filterValues[filterValueIndex] : null
}
value={filterValues ? filterValues[index] : null}
onChange={(newValue) => {
if (filterValueIndex === -1) {
onFiltersChange([newValue]);
} else {
filterValues.splice(
filterValueIndex,
1,
newValue,
);
onFiltersChange(filterValues);
}
const newFilterValues = filterValues?.length
? [...filterValues]
: [];
newFilterValues[index] = newValue;
onFiltersChange(newFilterValues);
}}
/>
</Fragment>
Expand Down
20 changes: 20 additions & 0 deletions src/components/search-wizard/SpecialFilter.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const Default = () => {
},
checkbox: null,
radio: null,
radio2: null,
date: null,
});

Expand Down Expand Up @@ -90,6 +91,25 @@ export const Default = () => {
onChange={getOnChange('radio')}
/>
<hr />
[Radio 2]
<SpecialFilter
config={{
type: 'radio',
options: [
{
value: '3',
alias: 'three',
},
{
value: '4',
alias: 'four',
},
],
}}
value={values.radio2}
onChange={getOnChange('radio2')}
/>
<hr />
[Date]
<SpecialFilter
config={{
Expand Down
8 changes: 6 additions & 2 deletions src/utah-design-system/RadioGroup.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as RadixRadioGroup from '@radix-ui/react-radio-group';
import { useId } from 'react';
import { twJoin, twMerge } from 'tailwind-merge';

/**
Expand All @@ -22,13 +23,16 @@ export default function RadioGroup({
onValueChange,
value,
}) {
const id = useId();

return (
<RadixRadioGroup.Root
aria-label={ariaLabel}
className={twMerge('flex flex-col items-start', className)}
defaultValue={defaultValue}
value={value}
name={id}
onValueChange={onValueChange}
value={value}
>
{items.map((item) => {
const id = `${ariaLabel}-${item.value}`;
Expand All @@ -44,7 +48,7 @@ export default function RadioGroup({
>
<RadixRadioGroup.Item
className={twJoin(
'flex h-4 w-4 items-center justify-center rounded-full border ',
'flex h-4 w-4 items-center justify-center rounded-full border',
item.disabled
? 'border-slate-300 bg-slate-50 data-[state=checked]:bg-slate-300 data-[state=checked]:text-white'
: 'border-slate-500 bg-white',
Expand Down
58 changes: 32 additions & 26 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,39 @@ export function getRelationships(tableName, allRelationships) {
export function getDefQueryFromLayerFilterValues(layerFilterValues) {
if (!layerFilterValues || layerFilterValues.length === 0) return null;

return layerFilterValues
.map((layerFilter) => {
switch (layerFilter.type) {
case 'field': {
const paddedValues =
layerFilter.fieldType === 'text'
? layerFilter.values.map((value) => `'${value}'`)
: layerFilter.values;

return `${layerFilter.field} IN (${paddedValues.join(',')})`;
return (
layerFilterValues
.map((layerFilter) => {
switch (layerFilter.type) {
case 'field': {
const paddedValues =
layerFilter.fieldType === 'text'
? layerFilter.values.map((value) => `'${value}'`)
: layerFilter.values;

return `${layerFilter.field} IN (${paddedValues.join(',')})`;
}
case 'radio': {
return layerFilter.values[0];
}
case 'checkbox': {
const paddedValues = layerFilter.values.map(
(value) => `(${value})`,
);

return `(${paddedValues.join(' OR ')})`;
}
case 'date': {
return `${layerFilter.field} >= '${layerFilter.values[0]}' AND ${layerFilter.field} <= '${layerFilter.values[1]}'`;
}
default:
throw new Error(`Invalid filter type: ${layerFilter.type}`);
}
case 'radio': {
return layerFilter.values[0];
}
case 'checkbox': {
const paddedValues = layerFilter.values.map((value) => `(${value})`);

return `(${paddedValues.join(' OR ')})`;
}
case 'date': {
return `${layerFilter.field} >= '${layerFilter.values[0]}' AND ${layerFilter.field} <= '${layerFilter.values[1]}'`;
}
default:
throw new Error(`Invalid filter type: ${layerFilter.type}`);
}
})
.join(' AND ');
})
// filter out empty values (this can happen if a filter has multiple filters)
.filter((value) => value)
.join(' AND ')
);
}

/** @typedef {import('./contexts/SearchMachineProvider').LayerFilterValue} LayerFilterValue */
Expand Down
28 changes: 26 additions & 2 deletions src/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ describe('getDefQueryFromLayerFilterValues', () => {
},
{
type: 'radio',
values: ["Field = 'Value1'"],
values: ["RadioField = 'RadioValue'"],
},
{
type: 'radio',
values: ["AnotherRadioField = 'AnotherRadioValue'"],
},
{
type: 'date',
Expand All @@ -106,14 +110,34 @@ describe('getDefQueryFromLayerFilterValues', () => {
const result = getDefQueryFromLayerFilterValues(values);

expect(result).toEqual(
"FieldName IN ('Value1','Value2') AND FieldName IN (1,2) AND ((Field = 'Value1') OR (Field = 'Value2')) AND Field = 'Value1' AND FieldName >= '2020-01-01' AND FieldName <= '2020-01-02'",
"FieldName IN ('Value1','Value2') AND FieldName IN (1,2) AND ((Field = 'Value1') OR (Field = 'Value2')) AND RadioField = 'RadioValue' AND AnotherRadioField = 'AnotherRadioValue' AND FieldName >= '2020-01-01' AND FieldName <= '2020-01-02'",
);
});

it('returns null if there are no values', () => {
expect(getDefQueryFromLayerFilterValues([])).toEqual(null);
expect(getDefQueryFromLayerFilterValues(undefined)).toEqual(null);
});

it('skips empty values', () => {
/** @type {import('./contexts/SearchMachineProvider').LayerFilterValue[]} */
const values = [
{
type: 'radio',
values: ["RadioField = 'RadioValue'"],
},
];
values[2] = {
type: 'radio',
values: ["AnotherRadioField = 'AnotherRadioValue'"],
};

const result = getDefQueryFromLayerFilterValues(values);

expect(result).toEqual(
"RadioField = 'RadioValue' AND AnotherRadioField = 'AnotherRadioValue'",
);
});
});

describe('getDefaultLayerFilterValues', () => {
Expand Down

0 comments on commit 2f13606

Please sign in to comment.