Skip to content

Commit

Permalink
feat: implement layer filters
Browse files Browse the repository at this point in the history
Except for queries that traverse relationships.

Closes #555
  • Loading branch information
stdavis committed Jan 30, 2024
1 parent 62c9e27 commit 0148ee3
Show file tree
Hide file tree
Showing 12 changed files with 684 additions and 82 deletions.
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"FACID",
"facilityust",
"FACNAME",
"FACTSHEET",
"FACTYPECODE",
"FACTYPEDESC",
"featureclass",
Expand Down Expand Up @@ -82,7 +83,10 @@
"noninteractive",
"ntlm",
"openpyxl",
"OPENREGAST",
"OPENRELEASE",
"opensgid",
"OPENTANK",
"osgeo",
"overscan",
"packagejson",
Expand All @@ -98,6 +102,7 @@
"pyshp",
"Qualtrics",
"reactfire",
"REGAST",
"remoteconfig",
"SANPETE",
"scursor",
Expand Down Expand Up @@ -151,4 +156,5 @@
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"testing.automaticallyOpenPeekView": "never",
"vitest.commandLine": "npx vitest",
}
15 changes: 14 additions & 1 deletion src/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useRemoteConfigValues } from '../contexts/RemoteConfigProvider';
import { useFirebase } from '../contexts/useFirebase';
import Spinner from '../utah-design-system/Spinner';
import Graphic from '@arcgis/core/Graphic';
import { getDefQueryFromLayerFilterValues } from '../utils';

const stateOfUtahPolygon = new Polygon(stateOfUtah);
const stateOfUtahExtent = stateOfUtahPolygon.extent;
Expand Down Expand Up @@ -316,9 +317,10 @@ export default function MapComponent() {
/**
* @param {import('../../functions/common/config').QueryLayerConfig} layer
* @param {import('../contexts/SearchMachineProvider').Filter} filter
* @param {string} specialFilterQuery
* @returns Promise
*/
async function searchLayer(layer, filter) {
async function searchLayer(layer, filter, specialFilterQuery) {
logEvent('search_layer', {
table_name: layer[fieldNames.queryLayers.tableName],
layer_name: layer[fieldNames.queryLayers.layerName],
Expand Down Expand Up @@ -361,7 +363,15 @@ export default function MapComponent() {
filter.attribute,
layer,
featureServiceJson.fields,
specialFilterQuery,
);

if (where) {
console.log(
`using where clause: "${where}" for ${layer[fieldNames.queryLayers.tableName]}`,
);
}

featureLayer.outFields = [featureServiceJson.objectIdField];
featureLayer.id = `${searchLayerIdPrefix}:${
layer[fieldNames.queryLayers.tableName]
Expand Down Expand Up @@ -461,6 +471,9 @@ export default function MapComponent() {
searchLayer(
getConfigByTableName(tableName, queryLayers),
state.context.filter,
getDefQueryFromLayerFilterValues(
state.context.layerFilterValues[tableName],
),
),
),
);
Expand Down
158 changes: 127 additions & 31 deletions src/components/search-wizard/QueryLayer.jsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,68 @@
import PropTypes from 'prop-types';
import { fieldNames } from '../../../functions/common/config';
import Checkbox from '../../utah-design-system/Checkbox';
import Icon from '../../utah-design-system/Icon';
import Tooltip from '../../utah-design-system/Tooltip';
import clsx from 'clsx';
import Popup, { CloseButton } from '../../utah-design-system/Popup';
import SpecialFilter from './SpecialFilter';
import { Fragment } from 'react';
import Button from '../../utah-design-system/Button';

/**
* @typedef {import('../../../functions/common/config').FieldFilterConfig} FieldFilterConfig
*
* @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
* @param {boolean} props.selected
* @param {(checked: boolean) => void} props.onSelectedChange
* @param {LayerFilterValue[]} [props.filterValues]
* @param {(
* newValues:
* | LayerFilterValue[]
* | ((oldValues: LayerFilterValue[]) => LayerFilterValue[]),
* ) => void} props.onFiltersChange
* @returns {JSX.Element}
*/
export default function QueryLayer({
config,
selected,
onSelectedChange,
// filter,
// onFilterChange,
filterValues,
onFiltersChange,
}) {
const id = `query-layer-${config[fieldNames.queryLayers.tableName]}`;
const specialFilters = config[fieldNames.queryLayers.specialFilters];

return (
<div className="my-2 flex items-center justify-between">
Expand All @@ -39,34 +83,86 @@ export default function QueryLayer({
{config[fieldNames.queryLayers.layerDescription]}
</div>
</Tooltip>
{config[fieldNames.queryLayers.metadataLink] ? (
<Tooltip
trigger={
<a
href={config[fieldNames.queryLayers.metadataLink]}
target="_blank"
rel="noreferrer"
>
<Icon
name="externalLink"
className="text-slate-600"
label="more info"
size="xs"
/>
</a>
}
>
More Info
</Tooltip>
) : null}
<div className="flex justify-center space-x-1">
{specialFilters.length ? (
<Popup
trigger={
<div className="relative">
{filterValues && filterValues.length > 0 ? (
<span className="absolute flex h-1.5 w-1.5 rounded-full bg-accent-dark"></span>
) : null}
<Icon
name="gear"
className={clsx(
'ml-0 cursor-pointer text-slate-600',
(!filterValues || filterValues.length === 0) &&
'opacity-50',
)}
label="filters"
/>
</div>
}
>
<div className="flex items-center justify-between">
<h5>Filter Layer</h5>
<CloseButton />
</div>
<div className="flex max-h-96 max-w-96 flex-col">
<div className="flex-1 overflow-y-auto">
{specialFilters.map((filterConfig, index) => {
const filterValueIndex = getFilterValueIndex(
filterConfig,
filterValues,
);
return (
<Fragment key={index}>
{index > 0 && <hr />}
<SpecialFilter
key={filterConfig.name}
config={filterConfig}
value={
filterValues ? filterValues[filterValueIndex] : null
}
onChange={(newValue) => {
if (filterValueIndex === -1) {
onFiltersChange([newValue]);
} else {
filterValues.splice(filterValueIndex, 1, newValue);
onFiltersChange(filterValues);
}
}}
/>
</Fragment>
);
})}
</div>
<Button className="w-full" onClick={() => onFiltersChange([])}>
Clear Filters
</Button>
</div>
</Popup>
) : null}
{config[fieldNames.queryLayers.metadataLink] ? (
<Tooltip
trigger={
<a
href={config[fieldNames.queryLayers.metadataLink]}
target="_blank"
rel="noreferrer"
>
<Icon
name="externalLink"
className="text-slate-600"
label="more info"
size="xs"
/>
</a>
}
>
More Info
</Tooltip>
) : null}
</div>
</div>
);
}

QueryLayer.propTypes = {
config: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired,
onSelectedChange: PropTypes.func.isRequired,
// filter: PropTypes.object,
// onFilterChange: PropTypes.func.isRequired,
};
55 changes: 48 additions & 7 deletions src/components/search-wizard/QueryLayer.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,75 @@ const config = {
'Coded Values': '',
};

/** @type {import('../../../functions/common/config').QueryLayerConfig} */
const noMetaLinkConfig = {
...config,
Name: 'No Metadata Link',
'Layer Name': 'No Metadata Link',
'Metadata Link': null,
};

/** @type {import('../../../functions/common/config').QueryLayerConfig} */
const filterConfig = {
...config,
'Layer Name': 'Filter',
'Special Filters': [
{
type: 'checkbox',
options: [
{
value: "ASSESSMENT = '1: Supports all designated uses'",
alias: '1: Supports all designated uses',
},
{
value: "ASSESSMENT = '2: Supports all assessed uses'",
alias: '2: Supports all assessed uses',
},
],
},
],
};

export const Default = () => (
<div className="w-80">
<QueryLayer
config={config}
selected={false}
onSelectedChange={console.log}
// filter={null}
// onFilterChange={console.log}
filterValues={undefined}
onFiltersChange={console.log}
/>
<QueryLayer
config={config}
selected={true}
onSelectedChange={console.log}
// filter={null}
// onFilterChange={console.log}
filterValues={undefined}
onFiltersChange={console.log}
/>
<QueryLayer
config={noMetaLinkConfig}
selected={false}
onSelectedChange={console.log}
// filter={null}
// onFilterChange={console.log}
filterValues={undefined}
onFiltersChange={console.log}
/>
<QueryLayer
config={filterConfig}
selected={false}
onSelectedChange={console.log}
filterValues={undefined}
onFiltersChange={console.log}
/>
<QueryLayer
config={filterConfig}
selected={false}
onSelectedChange={console.log}
filterValues={[
{
type: 'checkbox',
values: ["ASSESSMENT = '1: Supports all designated uses'"],
},
]}
onFiltersChange={console.log}
/>
</div>
);
8 changes: 8 additions & 0 deletions src/components/search-wizard/SelectMapData.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export default function SelectMapData({ queryLayers }) {
queryLayer[fieldNames.queryLayers.tableName],
})
}
filterValues={state.context.layerFilterValues[tableName]}
onFiltersChange={(newValues) => {
send({
type: 'UPDATE_LAYER_FILTER_VALUES',
tableName,
newValues,
});
}}
/>
);
})}
Expand Down
Loading

0 comments on commit 0148ee3

Please sign in to comment.