limit advanced search options by field type

This commit is contained in:
Keith J. Grant 2021-07-29 15:40:30 -07:00
parent aefc28a0ed
commit a0df379225
5 changed files with 237 additions and 172 deletions

View File

@ -200,7 +200,7 @@ DataListToolbar.propTypes = {
clearAllFilters: PropTypes.func,
qsConfig: QSConfig.isRequired,
searchColumns: SearchColumns.isRequired,
searchableKeys: PropTypes.arrayOf(PropTypes.string),
searchableKeys: PropTypes.arrayOf(PropTypes.string), // TODO: Update
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
sortColumns: SortColumns,
isAllSelected: PropTypes.bool,

View File

@ -16,6 +16,8 @@ import { SearchIcon, QuestionCircleIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import { useConfig } from 'contexts/Config';
import getDocsBaseUrl from 'util/getDocsBaseUrl';
import RelatedLookupTypeInput from './RelatedLookupTypeInput';
import LookupTypeInput from './LookupTypeInput';
const AdvancedGroup = styled.div`
display: flex;
@ -42,31 +44,36 @@ function AdvancedSearch({
// for now, I'm spreading set to get rid of duplicate keys...when they are grouped
// we might want to revisit that.
const allKeys = [
...new Set([...(searchableKeys || []), ...(relatedSearchableKeys || [])]),
...new Set([
...(searchableKeys.map((k) => k.key) || []),
...(relatedSearchableKeys || []),
]),
];
const [isPrefixDropdownOpen, setIsPrefixDropdownOpen] = useState(false);
const [isLookupDropdownOpen, setIsLookupDropdownOpen] = useState(false);
const [isKeyDropdownOpen, setIsKeyDropdownOpen] = useState(false);
const [prefixSelection, setPrefixSelection] = useState(null);
const [lookupSelection, setLookupSelection] = useState(null);
const [keySelection, setKeySelection] = useState(null);
const [searchValue, setSearchValue] = useState('');
const [relatedSearchKeySelected, setRelatedSearchKeySelected] =
useState(false);
// const [relatedSearchKeySelected, setRelatedSearchKeySelected] =
// useState(false);
const config = useConfig();
const relatedSearchKeySelected =
keySelection &&
relatedSearchableKeys.indexOf(keySelection) > -1 &&
!searchableKeys.find((k) => k.key === keySelection);
const lookupKeyType =
keySelection && !relatedSearchKeySelected
? searchableKeys.find((k) => k.key === keySelection).type
: null;
useEffect(() => {
if (
keySelection &&
relatedSearchableKeys.indexOf(keySelection) > -1 &&
searchableKeys.indexOf(keySelection) === -1
) {
if (relatedSearchKeySelected) {
setLookupSelection('name__icontains');
setRelatedSearchKeySelected(true);
} else {
setLookupSelection(null);
setRelatedSearchKeySelected(false);
}
}, [keySelection]); // eslint-disable-line react-hooks/exhaustive-deps
@ -136,160 +143,6 @@ function AdvancedSearch({
</Select>
);
const renderRelatedLookupType = () => (
<Select
ouiaId="set-lookup-typeahead"
aria-label={t`Related search type`}
className="lookupSelect"
variant={SelectVariant.typeahead}
typeAheadAriaLabel={t`Related search type typeahead`}
onToggle={setIsLookupDropdownOpen}
onSelect={(event, selection) => setLookupSelection(selection)}
selections={lookupSelection}
isOpen={isLookupDropdownOpen}
placeholderText={t`Related search type`}
maxHeight={maxSelectHeight}
noResultsFoundText={t`No results found`}
>
<SelectOption
id="name-option-select"
key="name__icontains"
value="name__icontains"
description={t`Fuzzy search on name field.`}
/>
<SelectOption
id="id-option-select"
key="id"
value="id"
description={t`Exact search on id field.`}
/>
{enableRelatedFuzzyFiltering && (
<SelectOption
id="search-option-select"
key="search"
value="search"
description={t`Fuzzy search on id, name or description fields.`}
/>
)}
</Select>
);
const renderLookupType = () => (
<Select
ouiaId="set-lookup-typeahead"
aria-label={t`Lookup select`}
className="lookupSelect"
variant={SelectVariant.typeahead}
typeAheadAriaLabel={t`Lookup typeahead`}
onToggle={setIsLookupDropdownOpen}
onSelect={(event, selection) => setLookupSelection(selection)}
onClear={() => setLookupSelection(null)}
selections={lookupSelection}
isOpen={isLookupDropdownOpen}
placeholderText={t`Lookup type`}
maxHeight={maxSelectHeight}
noResultsFoundText={t`No results found`}
>
<SelectOption
id="exact-option-select"
key="exact"
value="exact"
description={t`Exact match (default lookup if not specified).`}
/>
<SelectOption
id="iexact-option-select"
key="iexact"
value="iexact"
description={t`Case-insensitive version of exact.`}
/>
<SelectOption
id="contains-option-select"
key="contains"
value="contains"
description={t`Field contains value.`}
/>
<SelectOption
id="icontains-option-select"
key="icontains"
value="icontains"
description={t`Case-insensitive version of contains`}
/>
<SelectOption
id="startswith-option-select"
key="startswith"
value="startswith"
description={t`Field starts with value.`}
/>
<SelectOption
id="istartswith-option-select"
key="istartswith"
value="istartswith"
description={t`Case-insensitive version of startswith.`}
/>
<SelectOption
id="endswith-option-select"
key="endswith"
value="endswith"
description={t`Field ends with value.`}
/>
<SelectOption
id="iendswith-option-select"
key="iendswith"
value="iendswith"
description={t`Case-insensitive version of endswith.`}
/>
<SelectOption
id="regex-option-select"
key="regex"
value="regex"
description={t`Field matches the given regular expression.`}
/>
<SelectOption
id="iregex-option-select"
key="iregex"
value="iregex"
description={t`Case-insensitive version of regex.`}
/>
<SelectOption
id="gt-option-select"
key="gt"
value="gt"
description={t`Greater than comparison.`}
/>
<SelectOption
id="gte-option-select"
key="gte"
value="gte"
description={t`Greater than or equal to comparison.`}
/>
<SelectOption
id="lt-option-select"
key="lt"
value="lt"
description={t`Less than comparison.`}
/>
<SelectOption
id="lte-option-select"
key="lte"
value="lte"
description={t`Less than or equal to comparison.`}
/>
<SelectOption
id="isnull-option-select"
key="isnull"
value="isnull"
description={t`Check whether the given field or related object is null; expects a boolean value.`}
/>
<SelectOption
id="in-option-select"
key="in"
value="in"
description={t`Check whether the given field's value is present in the list provided; expects a comma-separated list of items.`}
/>
</Select>
);
return (
<AdvancedGroup>
{lookupSelection === 'search' ? (
@ -328,9 +181,21 @@ function AdvancedSearch({
</SelectOption>
))}
</Select>
{relatedSearchKeySelected
? renderRelatedLookupType()
: renderLookupType()}
{relatedSearchKeySelected ? (
<RelatedLookupTypeInput
value={lookupSelection}
setValue={setLookupSelection}
maxSelectHeight={maxSelectHeight}
enableFuzzyFiltering={enableRelatedFuzzyFiltering}
/>
) : (
<LookupTypeInput
value={lookupSelection}
type={lookupKeyType}
setValue={setLookupSelection}
maxSelectHeight={maxSelectHeight}
/>
)}
<InputGroup>
<TextInput
data-cy="advanced-search-text-input"

View File

@ -49,6 +49,7 @@ function OrganizationsList() {
OrganizationsAPI.read(params),
OrganizationsAPI.readOptions(),
]);
const keys = orgActions.data.actions?.GET || {};
return {
organizations: orgs.data.results,
organizationCount: orgs.data.count,
@ -56,9 +57,15 @@ function OrganizationsList() {
relatedSearchableKeys: (
orgActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(orgActions.data.actions?.GET || {}).filter(
(key) => orgActions.data.actions?.GET[key].filterable
),
// searchableKeys: Object.keys(orgActions.data.actions?.GET || {}).filter(
// (key) => orgActions.data.actions?.GET[key].filterable
// ),
searchableKeys: Object.keys(keys)
.filter((key) => keys[key].filterable)
.map((key) => ({
key,
type: keys[key].type,
})),
};
}, [location]),
{

View File

@ -0,0 +1,141 @@
import React, { useState } from 'react';
import { t } from '@lingui/macro';
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
function Option({ show, ...props }) {
if (!show) {
return null;
}
return <SelectOption {...props} />;
}
Option.defaultProps = {
show: true,
};
function LookupTypeInput({ value, type, setValue, maxSelectHeight }) {
const [isOpen, setIsOpen] = useState(false);
return (
<Select
ouiaId="set-lookup-typeahead"
aria-label={t`Lookup select`}
className="lookupSelect"
variant={SelectVariant.typeahead}
typeAheadAriaLabel={t`Lookup typeahead`}
onToggle={setIsOpen}
onSelect={(event, selection) => setValue(selection)}
onClear={() => setValue(null)}
selections={value}
isOpen={isOpen}
placeholderText={t`Lookup type`}
maxHeight={maxSelectHeight}
noResultsFoundText={t`No results found`}
>
<Option
id="exact-option-select"
key="exact"
value="exact"
description={t`Exact match (default lookup if not specified).`}
/>
<Option
id="iexact-option-select"
key="iexact"
value="iexact"
description={t`Case-insensitive version of exact.`}
show={type === 'string'}
/>
<Option
id="contains-option-select"
key="contains"
value="contains"
description={t`Field contains value.`}
show={type === 'string'}
/>
<Option
id="icontains-option-select"
key="icontains"
value="icontains"
description={t`Case-insensitive version of contains`}
show={type === 'string'}
/>
<Option
id="startswith-option-select"
key="startswith"
value="startswith"
description={t`Field starts with value.`}
show={type !== 'datetime'}
/>
<Option
id="istartswith-option-select"
key="istartswith"
value="istartswith"
description={t`Case-insensitive version of startswith.`}
show={type !== 'datetime'}
/>
<Option
id="endswith-option-select"
key="endswith"
value="endswith"
description={t`Field ends with value.`}
show={type !== 'datetime'}
/>
<Option
id="iendswith-option-select"
key="iendswith"
value="iendswith"
description={t`Case-insensitive version of endswith.`}
show={type !== 'datetime'}
/>
<Option
id="regex-option-select"
key="regex"
value="regex"
description={t`Field matches the given regular expression.`}
/>
<Option
id="iregex-option-select"
key="iregex"
value="iregex"
description={t`Case-insensitive version of regex.`}
/>
<Option
id="gt-option-select"
key="gt"
value="gt"
description={t`Greater than comparison.`}
/>
<Option
id="gte-option-select"
key="gte"
value="gte"
description={t`Greater than or equal to comparison.`}
/>
<Option
id="lt-option-select"
key="lt"
value="lt"
description={t`Less than comparison.`}
/>
<Option
id="lte-option-select"
key="lte"
value="lte"
description={t`Less than or equal to comparison.`}
/>
<Option
id="isnull-option-select"
key="isnull"
value="isnull"
description={t`Check whether the given field or related object is null; expects a boolean value.`}
/>
<Option
id="in-option-select"
key="in"
value="in"
description={t`Check whether the given field's value is present in the list provided; expects a comma-separated list of items.`}
/>
</Select>
);
}
export default LookupTypeInput;

View File

@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { t } from '@lingui/macro';
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
function RelatedLookupTypeInput({
value,
setValue,
maxSelectHeight,
enableFuzzyFiltering,
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<Select
ouiaId="set-lookup-typeahead"
aria-label={t`Related search type`}
className="lookupSelect"
variant={SelectVariant.typeahead}
typeAheadAriaLabel={t`Related search type typeahead`}
onToggle={setIsOpen}
onSelect={(event, selection) => setValue(selection)}
selections={value}
isOpen={isOpen}
placeholderText={t`Related search type`}
maxHeight={maxSelectHeight}
noResultsFoundText={t`No results found`}
>
<SelectOption
id="name-option-select"
key="name__icontains"
value="name__icontains"
description={t`Fuzzy search on name field.`}
/>
<SelectOption
id="id-option-select"
key="id"
value="id"
description={t`Exact search on id field.`}
/>
{enableFuzzyFiltering && (
<SelectOption
id="search-option-select"
key="search"
value="search"
description={t`Fuzzy search on id, name or description fields.`}
/>
)}
</Select>
);
}
export default RelatedLookupTypeInput;