Merge pull request #10798 from keithjgrant/7834-advanced-search-fix

Only allow legal/logical match types in advanced search
This commit is contained in:
Sarah Akus 2021-08-26 14:39:40 -04:00 committed by GitHub
commit cdce745c55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 434 additions and 384 deletions

View File

@ -9,6 +9,7 @@ import { CredentialsAPI } from 'api';
import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
import useRequest from 'hooks/useRequest';
import { required } from 'util/validators';
import { getSearchableKeys } from 'components/PaginatedTable';
import Popover from '../Popover';
import ContentError from '../ContentError';
@ -59,9 +60,7 @@ function AdHocCredentialStep({ credentialTypeId }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [credentialTypeId, history.location.search]),
{

View File

@ -6,6 +6,7 @@ import { Form, FormGroup } from '@patternfly/react-core';
import { ExecutionEnvironmentsAPI } from 'api';
import { parseQueryString, getQSConfig, mergeParams } from 'util/qs';
import { getSearchableKeys } from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import Popover from '../Popover';
import ContentError from '../ContentError';
@ -60,9 +61,7 @@ function AdHocExecutionEnvironmentStep({ organizationId }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location.search, organizationId]),
{

View File

@ -8,7 +8,11 @@ import { getQSConfig, parseQueryString } from 'util/qs';
import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../CheckboxListItem';
import { SelectedList } from '../SelectedList';
import PaginatedTable, { HeaderCell, HeaderRow } from '../PaginatedTable';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from '../PaginatedTable';
const QS_Config = (sortColumns) =>
getQSConfig('resource', {
@ -56,9 +60,7 @@ function SelectResourceStep({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location, fetchItems, fetchOptions, sortColumns]),
{

View File

@ -3,6 +3,7 @@ import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Button, Modal } from '@patternfly/react-core';
import { getSearchableKeys } from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import { getQSConfig, parseQueryString } from 'util/qs';
import useSelected from 'hooks/useSelected';
@ -53,9 +54,7 @@ function AssociateModal({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [fetchRequest, optionsRequest, history.location.search, displayKey]),
{

View File

@ -20,7 +20,7 @@ import {
AngleRightIcon,
SearchIcon,
} from '@patternfly/react-icons';
import { SearchColumns, SortColumns, QSConfig } from 'types';
import { SearchColumns, SortColumns, QSConfig, SearchableKeys } from 'types';
import { KebabifiedProvider } from 'contexts/Kebabified';
import ExpandCollapse from '../ExpandCollapse';
import Search from '../Search';
@ -200,7 +200,7 @@ DataListToolbar.propTypes = {
clearAllFilters: PropTypes.func,
qsConfig: QSConfig.isRequired,
searchColumns: SearchColumns.isRequired,
searchableKeys: PropTypes.arrayOf(PropTypes.string),
searchableKeys: SearchableKeys,
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
sortColumns: SortColumns,
isAllSelected: PropTypes.bool,

View File

@ -20,6 +20,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarDeleteButton,
getSearchableKeys,
} from '../PaginatedTable';
import JobListItem from './JobListItem';
import JobListCancelButton from './JobListCancelButton';
@ -80,9 +81,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
},
[location] // eslint-disable-line react-hooks/exhaustive-deps

View File

@ -7,6 +7,7 @@ import { useField } from 'formik';
import styled from 'styled-components';
import { Alert, ToolbarItem } from '@patternfly/react-core';
import { CredentialsAPI, CredentialTypesAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import AnsibleSelect from '../../AnsibleSelect';
@ -88,9 +89,7 @@ function CredentialsStep({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [selectedType, history.location.search]),
{ credentials: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] }

View File

@ -6,6 +6,7 @@ import { useField } from 'formik';
import styled from 'styled-components';
import { Alert } from '@patternfly/react-core';
import { InventoriesAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import OptionsList from '../../OptionsList';
@ -45,9 +46,7 @@ function InventoryStep({ warningMessage = null }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location]),
{

View File

@ -10,7 +10,7 @@ import {
removeParams,
updateQueryString,
} from 'util/qs';
import { QSConfig, SearchColumns, SortColumns } from 'types';
import { QSConfig, SearchColumns, SortColumns, SearchableKeys } from 'types';
import DataListToolbar from '../DataListToolbar';
const EmptyStateControlsWrapper = styled.div`
@ -146,7 +146,7 @@ ListHeader.propTypes = {
itemCount: PropTypes.number.isRequired,
qsConfig: QSConfig.isRequired,
searchColumns: SearchColumns.isRequired,
searchableKeys: PropTypes.arrayOf(PropTypes.string),
searchableKeys: SearchableKeys,
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
sortColumns: SortColumns,
renderToolbar: PropTypes.func,

View File

@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { FormGroup } from '@patternfly/react-core';
import { ApplicationsAPI } from 'api';
import { Application } from 'types';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import Lookup from './Lookup';
@ -42,9 +43,7 @@ function ApplicationLookup({ onChange, value, label, fieldName, validate }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse?.data?.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse?.data?.actions?.GET),
};
}, [location]),
{

View File

@ -14,6 +14,7 @@ import { t } from '@lingui/macro';
import { FormGroup } from '@patternfly/react-core';
import { CredentialsAPI } from 'api';
import { Credential } from 'types';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
import useRequest from 'hooks/useRequest';
@ -82,12 +83,10 @@ function CredentialLookup({
autoPopulateLookup(data.results);
}
const searchKeys = Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable);
const item = searchKeys.indexOf('type');
const searchKeys = getSearchableKeys(actionsResponse.data.actions?.GET);
const item = searchKeys.find((k) => k.key === 'type');
if (item) {
searchKeys[item] = 'credential_type__kind';
item.key = 'credential_type__kind';
}
return {

View File

@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { FormGroup, Tooltip } from '@patternfly/react-core';
import { ExecutionEnvironmentsAPI, ProjectsAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import { ExecutionEnvironment } from 'types';
import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
import useRequest from 'hooks/useRequest';
@ -109,9 +110,7 @@ function ExecutionEnvironmentLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [
location,

View File

@ -21,7 +21,11 @@ import ChipGroup from '../ChipGroup';
import Popover from '../Popover';
import DataListToolbar from '../DataListToolbar';
import LookupErrorMessage from './shared/LookupErrorMessage';
import PaginatedTable, { HeaderCell, HeaderRow } from '../PaginatedTable';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from '../PaginatedTable';
import HostListItem from './HostListItem';
import {
removeDefaultParams,
@ -157,9 +161,7 @@ function HostFilterLookup({
relatedSearchableKeys: (actions?.related_search_fields || []).map(
parseRelatedSearchFields
),
searchableKeys: Object.keys(actions?.actions.GET || {}).filter(
(key) => actions.actions?.GET[key].filterable
),
searchableKeys: getSearchableKeys(actions?.actions.GET),
};
},
[location.search]

View File

@ -6,6 +6,7 @@ import { t, Trans } from '@lingui/macro';
import { FormGroup } from '@patternfly/react-core';
import { InstanceGroupsAPI } from 'api';
import { InstanceGroup } from 'types';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import Popover from '../Popover';
@ -47,9 +48,7 @@ function InstanceGroupsLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location]),
{

View File

@ -74,14 +74,17 @@ function InventoryLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => {
if (['kind', 'host_filter'].includes(key) && hideSmartInventories) {
return false;
}
return actionsResponse.data.actions?.GET[key].filterable;
}),
searchableKeys: Object.keys(actionsResponse.data.actions?.GET || {})
.filter((key) => {
if (['kind', 'host_filter'].includes(key) && hideSmartInventories) {
return false;
}
return actionsResponse.data.actions?.GET[key].filterable;
})
.map((key) => ({
key,
type: actionsResponse.data.actions?.GET[key].type,
})),
canEdit:
Boolean(actionsResponse.data.actions.POST) || isOverrideDisabled,
};

View File

@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { t } from '@lingui/macro';
import { ToolbarItem, Alert } from '@patternfly/react-core';
import { CredentialsAPI, CredentialTypesAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import { getQSConfig, parseQueryString } from 'util/qs';
import useIsMounted from 'hooks/useIsMounted';
@ -100,9 +101,7 @@ function MultiCredentialsLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [selectedType, history.location]),
{

View File

@ -6,6 +6,7 @@ import { FormGroup } from '@patternfly/react-core';
import { OrganizationsAPI } from 'api';
import { Organization } from 'types';
import { getQSConfig, parseQueryString } from 'util/qs';
import { getSearchableKeys } from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
import OptionsList from '../OptionsList';
@ -57,9 +58,7 @@ function OrganizationLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [autoPopulate, autoPopulateLookup, history.location.search]),
{

View File

@ -7,6 +7,7 @@ import { ProjectsAPI } from 'api';
import { Project } from 'types';
import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
import useRequest from 'hooks/useRequest';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import OptionsList from '../OptionsList';
import Popover from '../Popover';
@ -56,9 +57,7 @@ function ProjectLookup({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
canEdit:
Boolean(actionsResponse.data.actions.POST) || isOverrideDisabled,
};

View File

@ -9,7 +9,11 @@ import { NotificationTemplatesAPI } from 'api';
import AlertModal from '../AlertModal';
import ErrorDetail from '../ErrorDetail';
import NotificationListItem from './NotificationListItem';
import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable';
import PaginatedTable, {
HeaderRow,
HeaderCell,
getSearchableKeys,
} from '../PaginatedTable';
const QS_CONFIG = getQSConfig('notification', {
page: 1,
@ -89,9 +93,7 @@ function NotificationList({
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
if (showApprovalsToggle) {

View File

@ -7,7 +7,7 @@ import { t } from '@lingui/macro';
import { useLocation, useHistory } from 'react-router-dom';
import { parseQueryString, updateQueryString } from 'util/qs';
import { QSConfig, SearchColumns } from 'types';
import { QSConfig, SearchColumns, SearchableKeys } from 'types';
import ListHeader from '../ListHeader';
import ContentEmpty from '../ContentEmpty';
import ContentError from '../ContentError';
@ -184,7 +184,7 @@ PaginatedTable.propTypes = {
qsConfig: QSConfig.isRequired,
renderRow: PropTypes.func.isRequired,
toolbarSearchColumns: SearchColumns,
toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string),
toolbarSearchableKeys: SearchableKeys,
toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
showPageSizeOptions: PropTypes.bool,
renderToolbar: PropTypes.func,

View File

@ -0,0 +1,8 @@
export default function getSearchableKeys(keys = {}) {
return Object.keys(keys)
.filter((key) => keys[key].filterable)
.map((key) => ({
key,
type: keys[key].type,
}));
}

View File

@ -5,3 +5,4 @@ export { default as ActionItem } from './ActionItem';
export { default as ToolbarDeleteButton } from './ToolbarDeleteButton';
export { default as ToolbarAddButton } from './ToolbarAddButton';
export { default as ToolbarSyncSourceButton } from './ToolbarSyncSourceButton';
export { default as getSearchableKeys } from './getSearchableKeys';

View File

@ -11,6 +11,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from '../PaginatedTable';
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
import ResourceAccessListItem from './ResourceAccessListItem';
@ -89,9 +90,7 @@ function ResourceAccessList({ apiModel, resource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
organizationRoles: orgRoles,
};
}, [apiModel, location, resource]),

View File

@ -14,6 +14,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from '../../PaginatedTable';
import DataListToolbar from '../../DataListToolbar';
import ScheduleListItem from './ScheduleListItem';
@ -61,9 +62,7 @@ function ScheduleList({
relatedSearchableKeys: (
scheduleActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
scheduleActions.data.actions?.GET || {}
).filter((key) => scheduleActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(scheduleActions.data.actions?.GET),
};
}, [location.search, loadSchedules, loadScheduleOptions]),
{

View File

@ -1,6 +1,6 @@
import 'styled-components/macro';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { string, func, bool, arrayOf } from 'prop-types';
import { t } from '@lingui/macro';
import {
Button,
@ -16,6 +16,9 @@ import { SearchIcon, QuestionCircleIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import { useConfig } from 'contexts/Config';
import getDocsBaseUrl from 'util/getDocsBaseUrl';
import { SearchableKeys } from 'types';
import RelatedLookupTypeInput from './RelatedLookupTypeInput';
import LookupTypeInput from './LookupTypeInput';
const AdvancedGroup = styled.div`
display: flex;
@ -42,31 +45,33 @@ 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 config = useConfig();
const selectedKey = searchableKeys.find((k) => k.key === keySelection);
const relatedSearchKeySelected =
keySelection &&
relatedSearchableKeys.indexOf(keySelection) > -1 &&
!selectedKey;
const lookupKeyType =
keySelection && !relatedSearchKeySelected ? selectedKey?.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 +141,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 +179,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"
@ -369,12 +232,12 @@ function AdvancedSearch({
}
AdvancedSearch.propTypes = {
onSearch: PropTypes.func.isRequired,
searchableKeys: PropTypes.arrayOf(PropTypes.string),
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
maxSelectHeight: PropTypes.string,
enableNegativeFiltering: PropTypes.bool,
enableRelatedFuzzyFiltering: PropTypes.bool,
onSearch: func.isRequired,
searchableKeys: SearchableKeys,
relatedSearchableKeys: arrayOf(string),
maxSelectHeight: string,
enableNegativeFiltering: bool,
enableRelatedFuzzyFiltering: bool,
};
AdvancedSearch.defaultProps = {

View File

@ -10,22 +10,14 @@ describe('<AdvancedSearch />', () => {
jest.clearAllMocks();
});
test('initially renders without crashing', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={jest.fn}
searchableKeys={[]}
relatedSearchableKeys={[]}
/>
);
expect(wrapper.length).toBe(1);
});
test('Remove duplicates from searchableKeys/relatedSearchableKeys list', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={jest.fn}
searchableKeys={['foo', 'bar']}
searchableKeys={[
{ key: 'foo', type: 'string' },
{ key: 'bar', type: 'string' },
]}
relatedSearchableKeys={['bar', 'baz']}
/>
);
@ -42,7 +34,10 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={advancedSearchMock}
searchableKeys={['foo', 'bar']}
searchableKeys={[
{ key: 'foo', type: 'string' },
{ key: 'bar', type: 'string' },
]}
relatedSearchableKeys={['bar', 'baz']}
/>
);
@ -155,7 +150,10 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={advancedSearchMock}
searchableKeys={['foo', 'bar']}
searchableKeys={[
{ key: 'foo', type: 'string' },
{ key: 'bar', type: 'string' },
]}
relatedSearchableKeys={['bar', 'baz']}
/>
);
@ -239,7 +237,7 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={advancedSearchMock}
searchableKeys={['foo']}
searchableKeys={[{ key: 'foo', type: 'string' }]}
relatedSearchableKeys={[]}
/>
);
@ -278,7 +276,7 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={advancedSearchMock}
searchableKeys={['foo']}
searchableKeys={[{ key: 'foo', type: 'string' }]}
relatedSearchableKeys={[]}
/>
);
@ -375,7 +373,10 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={jest.fn}
searchableKeys={['foo', 'bar']}
searchableKeys={[
{ key: 'foo', type: 'string' },
{ key: 'bar', type: 'string' },
]}
relatedSearchableKeys={['bar', 'baz']}
enableNegativeFiltering={false}
/>
@ -399,7 +400,10 @@ describe('<AdvancedSearch />', () => {
wrapper = mountWithContexts(
<AdvancedSearch
onSearch={jest.fn}
searchableKeys={['foo', 'bar']}
searchableKeys={[
{ key: 'foo', type: 'string' },
{ key: 'bar', type: 'string' },
]}
relatedSearchableKeys={['bar', 'baz']}
enableRelatedFuzzyFiltering={false}
/>

View File

@ -0,0 +1,157 @@
import React, { useState } from 'react';
import { string, oneOfType, arrayOf, func } from 'prop-types';
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.`}
show={type !== 'json'}
/>
<Option
id="gte-option-select"
key="gte"
value="gte"
description={t`Greater than or equal to comparison.`}
show={type !== 'json'}
/>
<Option
id="lt-option-select"
key="lt"
value="lt"
description={t`Less than comparison.`}
show={type !== 'json'}
/>
<Option
id="lte-option-select"
key="lte"
value="lte"
description={t`Less than or equal to comparison.`}
show={type !== 'json'}
/>
<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>
);
}
LookupTypeInput.propTypes = {
type: string,
value: oneOfType([string, arrayOf(string)]),
setValue: func.isRequired,
maxSelectHeight: string,
};
LookupTypeInput.defaultProps = {
type: 'string',
value: '',
maxSelectHeight: '300px',
};
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;

View File

@ -19,7 +19,7 @@ import {
import { SearchIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import { parseQueryString } from 'util/qs';
import { QSConfig, SearchColumns } from 'types';
import { QSConfig, SearchColumns, SearchableKeys } from 'types';
import AdvancedSearch from './AdvancedSearch';
import getChipsByKey from './getChipsByKey';
@ -276,6 +276,7 @@ Search.propTypes = {
maxSelectHeight: PropTypes.string,
enableNegativeFiltering: PropTypes.bool,
enableRelatedFuzzyFiltering: PropTypes.bool,
searchableKeys: SearchableKeys,
};
Search.defaultProps = {
@ -285,6 +286,7 @@ Search.defaultProps = {
maxSelectHeight: '300px',
enableNegativeFiltering: true,
enableRelatedFuzzyFiltering: true,
searchableKeys: [],
};
export default Search;

View File

@ -20,6 +20,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarDeleteButton,
getSearchableKeys,
} from '../PaginatedTable';
import AddDropDownButton from '../AddDropDownButton';
import TemplateListItem from './TemplateListItem';
@ -69,9 +70,7 @@ function TemplateList({ defaultParams }) {
relatedSearchableKeys: (
responses[3]?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responses[3].data.actions?.GET || {}
).filter((key) => responses[3].data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responses[3].data.actions?.GET),
};
}, [location]), // eslint-disable-line react-hooks/exhaustive-deps
{

View File

@ -17,6 +17,7 @@ import DatalistToolbar from 'components/DataListToolbar';
import PaginatedTable, {
HeaderRow,
HeaderCell,
getSearchableKeys,
} from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import { getQSConfig, parseQueryString, updateQueryString } from 'util/qs';
@ -72,9 +73,7 @@ function ActivityStream() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
},
[location] // eslint-disable-line react-hooks/exhaustive-deps

View File

@ -6,6 +6,7 @@ import PaginatedTable, {
HeaderCell,
HeaderRow,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import { TokensAPI, ApplicationsAPI } from 'api';
@ -57,9 +58,7 @@ function ApplicationTokenList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [id, location.search]),
{ tokens: [], itemCount: 0, relatedSearchableKeys: [], searchableKeys: [] }

View File

@ -16,6 +16,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarDeleteButton,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useSelected from 'hooks/useSelected';
@ -57,9 +58,7 @@ function ApplicationsList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useRequest, { useDeleteItems } from 'hooks/useRequest';
import { getQSConfig, parseQueryString } from 'util/qs';
@ -44,9 +45,7 @@ function CredentialList() {
CredentialsAPI.read(params),
CredentialsAPI.readOptions(),
]);
const searchKeys = Object.keys(
credActions.data.actions?.GET || {}
).filter((key) => credActions.data.actions?.GET[key].filterable);
const searchKeys = getSearchableKeys(credActions.data.actions?.GET);
const item = searchKeys.indexOf('type');
if (item) {
searchKeys[item] = 'credential_type__kind';

View File

@ -12,6 +12,7 @@ import useRequest from 'hooks/useRequest';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('credential', {
@ -44,9 +45,7 @@ function CredentialsStep() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location.search]),
{ credentials: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] }

View File

@ -13,6 +13,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarDeleteButton,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import ErrorDetail from 'components/ErrorDetail';
import AlertModal from 'components/AlertModal';
@ -57,9 +58,7 @@ function CredentialTypeList() {
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location]),
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarDeleteButton,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import ErrorDetail from 'components/ErrorDetail';
import AlertModal from 'components/AlertModal';
@ -56,9 +57,7 @@ function ExecutionEnvironmentList() {
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location]),
{

View File

@ -11,6 +11,7 @@ import DatalistToolbar from 'components/DataListToolbar';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem';
@ -56,9 +57,7 @@ function ExecutionEnvironmentTemplateList({ executionEnvironment }) {
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location, id]),
{

View File

@ -15,6 +15,7 @@ import PaginatedTable, {
HeaderCell,
HeaderRow,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import AssociateModal from 'components/AssociateModal';
import DisassociateButton from 'components/DisassociateButton';
@ -66,9 +67,7 @@ function HostGroupsList({ host }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [hostId, search]),
{

View File

@ -11,6 +11,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useRequest, { useDeleteItems } from 'hooks/useRequest';
import useSelected from 'hooks/useSelected';
@ -68,9 +69,7 @@ function HostList() {
relatedSearchableKeys: (
results[1]?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(results[1].data.actions?.GET || {}).filter(
(key) => results[1].data.actions?.GET[key].filterable
),
searchableKeys: getSearchableKeys(results[1].data.actions?.GET),
};
}, [location]),
{

View File

@ -13,6 +13,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import ErrorDetail from 'components/ErrorDetail';
import AlertModal from 'components/AlertModal';
@ -107,9 +108,7 @@ function InstanceGroupList({
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location]),
{

View File

@ -9,6 +9,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import DisassociateButton from 'components/DisassociateButton';
import AssociateModal from 'components/AssociateModal';
@ -61,9 +62,7 @@ function InstanceList() {
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location.search, instanceGroupId]),
{

View File

@ -17,6 +17,7 @@ import ErrorDetail from 'components/ErrorDetail';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
import AssociateModal from 'components/AssociateModal';
import DisassociateButton from 'components/DisassociateButton';
@ -67,9 +68,7 @@ function InventoryGroupHostList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [groupId, inventoryId, location.search]),
{

View File

@ -11,6 +11,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
import InventoryGroupItem from './InventoryGroupItem';
@ -62,9 +63,7 @@ function InventoryGroupsList() {
relatedSearchableKeys: (
groupOptions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
groupOptions.data.actions?.GET || {}
).filter((key) => groupOptions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(groupOptions.data.actions?.GET),
};
}, [inventoryId, location]),
{

View File

@ -16,6 +16,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import AssociateModal from 'components/AssociateModal';
import DisassociateButton from 'components/DisassociateButton';
@ -72,9 +73,7 @@ function InventoryHostGroupsList() {
relatedSearchableKeys: (
hostGroupOptions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
hostGroupOptions.data.actions?.GET || {}
).filter((key) => hostGroupOptions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(hostGroupOptions.data.actions?.GET),
};
}, [hostId, search]), // eslint-disable-line react-hooks/exhaustive-deps
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useSelected from 'hooks/useSelected';
import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
@ -59,9 +60,7 @@ function InventoryHostList() {
relatedSearchableKeys: (
hostOptions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(hostOptions.data.actions?.GET || {}).filter(
(key) => hostOptions.data.actions?.GET[key].filterable
),
searchableKeys: getSearchableKeys(hostOptions.data.actions?.GET),
};
}, [id, search]),
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import AddDropDownButton from 'components/AddDropDownButton';
@ -54,9 +55,7 @@ function InventoryList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -13,6 +13,7 @@ import DataListToolbar from 'components/DataListToolbar';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
import AddDropDownButton from 'components/AddDropDownButton';
import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
@ -65,9 +66,7 @@ function InventoryRelatedGroupList() {
relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
(val) => val.slice(0, -8)
),
searchableKeys: Object.keys(actions.data.actions?.GET || {}).filter(
(key) => actions.data.actions?.GET[key].filterable
),
searchableKeys: getSearchableKeys(actions.data.actions?.GET),
canAdd:
actions.data.actions &&
Object.prototype.hasOwnProperty.call(actions.data.actions, 'POST'),

View File

@ -15,6 +15,7 @@ import { InventorySourcesAPI } from 'api';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import RoutedTabs from 'components/RoutedTabs';
import { getSearchableKeys } from 'components/PaginatedTable';
import useRequest from 'hooks/useRequest';
import { getJobModel } from 'util/jobs';
import JobDetail from './JobDetail';
@ -83,9 +84,7 @@ function Job({ setBreadcrumb }) {
eventRelatedSearchableKeys: (
eventOptions?.related_search_fields || []
).map((val) => val.slice(0, -8)),
eventSearchableKeys: Object.keys(
eventOptions?.actions?.GET || {}
).filter((key) => eventOptions?.actions?.GET[key].filterable),
eventSearchableKeys: getSearchableKeys(eventOptions?.actions?.GET),
};
}, [id, type, setBreadcrumb]),
{

View File

@ -11,6 +11,7 @@ import ErrorDetail from 'components/ErrorDetail';
import PaginatedTable, {
HeaderRow,
HeaderCell,
getSearchableKeys,
} from 'components/PaginatedTable';
import { useConfig } from 'contexts/Config';
import { parseQueryString, getQSConfig } from 'util/qs';
@ -25,9 +26,7 @@ const QS_CONFIG = getQSConfig('system_job_templates', {
const buildSearchKeys = (options) => {
const actions = options?.data?.actions?.GET || {};
const searchableKeys = Object.keys(actions).filter(
(key) => actions[key].filterable
);
const searchableKeys = getSearchableKeys(actions);
const relatedSearchableKeys = options?.data?.related_search_fields || [];
return { searchableKeys, relatedSearchableKeys };

View File

@ -15,6 +15,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail';
@ -62,9 +63,7 @@ function NotificationTemplatesList() {
relatedSearchableKeys: (
actionsResponse.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -5,6 +5,7 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import NotificationTemplateListItem from './NotificationTemplateListItem';
jest.mock('../../../api/models/NotificationTemplates');
jest.mock('../../../api/models/Notifications');
const template = {
id: 3,

View File

@ -10,6 +10,7 @@ import useRequest from 'hooks/useRequest';
import PaginatedTable, {
HeaderRow,
HeaderCell,
getSearchableKeys,
} from 'components/PaginatedTable';
import DatalistToolbar from 'components/DataListToolbar';
@ -51,9 +52,7 @@ function OrganizationExecEnvList({ organization }) {
relatedSearchableKeys: (
responseActions?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
responseActions.data.actions?.GET || {}
).filter((key) => responseActions.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
};
}, [location, id]),
{

View File

@ -13,6 +13,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useSelected from 'hooks/useSelected';
@ -56,9 +57,7 @@ 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: getSearchableKeys(orgActions.data.actions?.GET),
};
}, [location]),
{

View File

@ -7,6 +7,7 @@ import { OrganizationsAPI } from 'api';
import PaginatedTable, {
HeaderRow,
HeaderCell,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
@ -39,9 +40,7 @@ function OrganizationTeamList({ id }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [id, location]),
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useSelected from 'hooks/useSelected';
@ -54,9 +55,7 @@ function ProjectJobTemplatesList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location, projectId]),
{

View File

@ -15,6 +15,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useSelected from 'hooks/useSelected';
import useExpanded from 'hooks/useExpanded';
@ -75,9 +76,7 @@ function ProjectList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -14,6 +14,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useSelected from 'hooks/useSelected';
import { getQSConfig, parseQueryString } from 'util/qs';
@ -55,9 +56,7 @@ function TeamList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -16,6 +16,7 @@ import PaginatedTable, {
HeaderCell,
HeaderRow,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import ErrorDetail from 'components/ErrorDetail';
@ -69,9 +70,7 @@ function TeamRolesList({ me, team }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [me.id, team.id, team.organization, search]),
{

View File

@ -11,6 +11,7 @@ import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('inventory-sources', {
@ -40,9 +41,7 @@ function InventorySourcesList({ nodeResource, onUpdateNodeResource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -11,6 +11,7 @@ import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('job-templates', {
@ -42,9 +43,7 @@ function JobTemplatesList({ nodeResource, onUpdateNodeResource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -11,6 +11,7 @@ import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('projects', {
@ -40,9 +41,7 @@ function ProjectsList({ nodeResource, onUpdateNodeResource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -10,6 +10,7 @@ import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('system-job-templates', {
@ -46,9 +47,7 @@ function SystemJobTemplatesList({ nodeResource, onUpdateNodeResource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -11,6 +11,7 @@ import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
getSearchableKeys,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('workflow-job-templates', {
@ -47,9 +48,7 @@ function WorkflowJobTemplatesList({ nodeResource, onUpdateNodeResource }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -12,6 +12,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useRequest, { useDeleteItems } from 'hooks/useRequest';
import useSelected from 'hooks/useSelected';
@ -53,9 +54,7 @@ function UserList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -16,6 +16,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import ErrorDetail from 'components/ErrorDetail';
import AlertModal from 'components/AlertModal';
@ -67,9 +68,7 @@ function UserRolesList({ user }) {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [user.id, search]),
{

View File

@ -6,6 +6,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarAddButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import DataListToolbar from 'components/DataListToolbar';
import DisassociateButton from 'components/DisassociateButton';
@ -66,9 +67,7 @@ function UserTeamList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [userId, location.search]),
{

View File

@ -8,6 +8,7 @@ import PaginatedTable, {
HeaderCell,
ToolbarAddButton,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import useSelected from 'hooks/useSelected';
import useRequest, { useDeleteItems } from 'hooks/useRequest';
@ -58,9 +59,7 @@ function UserTokenList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [id, location.search]),
{ tokens: [], itemCount: 0, relatedSearchableKeys: [], searchableKeys: [] }

View File

@ -7,6 +7,7 @@ import PaginatedTable, {
HeaderRow,
HeaderCell,
ToolbarDeleteButton,
getSearchableKeys,
} from 'components/PaginatedTable';
import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail';
@ -50,9 +51,7 @@ function WorkflowApprovalsList() {
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [location]),
{

View File

@ -421,3 +421,10 @@ export const ExecutionEnvironment = shape({
description: string,
pull: string,
});
export const SearchableKeys = arrayOf(
shape({
key: string.isRequired,
type: string.isRequired,
})
);