mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
limit advanced search options by field type
This commit is contained in:
parent
aefc28a0ed
commit
a0df379225
@ -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,
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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]),
|
||||
{
|
||||
|
||||
141
awx/ui_next/src/components/Search/LookupTypeInput.js
Normal file
141
awx/ui_next/src/components/Search/LookupTypeInput.js
Normal 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;
|
||||
52
awx/ui_next/src/components/Search/RelatedLookupTypeInput.js
Normal file
52
awx/ui_next/src/components/Search/RelatedLookupTypeInput.js
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user