Updates to support advanced search changes:

- make set type and lookup prefixes/suffixes on searchColumns explicitly defined
- send possible search keys from options requests on (most) lists
This commit is contained in:
John Mitchell
2020-07-28 14:57:55 -04:00
parent b46a87209a
commit 5ffc2e4188
57 changed files with 875 additions and 263 deletions

View File

@@ -11,6 +11,10 @@ class Applications extends Base {
params, params,
}); });
} }
readTokenOptions(appId) {
return this.http.options(`${this.baseUrl}${appId}/tokens/`);
}
} }
export default Applications; export default Applications;

View File

@@ -60,6 +60,10 @@ class Users extends Base {
params, params,
}); });
} }
readTokenOptions(userId) {
return this.http.options(`${this.baseUrl}${userId}/tokens/`);
}
} }
export default Users; export default Users;

View File

@@ -156,31 +156,31 @@ class AddResourceRole extends React.Component {
const userSearchColumns = [ const userSearchColumns = [
{ {
name: i18n._(t`Username`), name: i18n._(t`Username`),
key: 'username', key: 'username__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`First Name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name__icontains',
}, },
{ {
name: i18n._(t`Last Name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name__icontains',
}, },
]; ];
const userSortColumns = [ const userSortColumns = [
{ {
name: i18n._(t`Username`), name: i18n._(t`Username`),
key: 'username', key: 'username__icontains',
}, },
{ {
name: i18n._(t`First Name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name__icontains',
}, },
{ {
name: i18n._(t`Last Name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name__icontains',
}, },
]; ];

View File

@@ -13,7 +13,7 @@ describe('<SelectResourceStep />', () => {
const searchColumns = [ const searchColumns = [
{ {
name: 'Username', name: 'Username',
key: 'username', key: 'username__icontains',
isDefault: true, isDefault: true,
}, },
]; ];

View File

@@ -114,16 +114,16 @@ function AssociateModal({
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[

View File

@@ -38,7 +38,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const location = useLocation(); const location = useLocation();
const { const {
result: { results, count }, result: { results, count, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchJobs, request: fetchJobs,
@@ -46,12 +46,27 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
useCallback( useCallback(
async () => { async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const { data } = await UnifiedJobsAPI.read({ ...params }); const [response, actionsResponse] = await Promise.all([
return data; UnifiedJobsAPI.read({ ...params }),
UnifiedJobsAPI.readOptions(),
]);
return {
results: response.data.results,
count: response.data.count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, },
[location] // eslint-disable-line react-hooks/exhaustive-deps [location] // eslint-disable-line react-hooks/exhaustive-deps
), ),
{ results: [], count: 0 } {
results: [],
count: 0,
actions: {},
relatedSearchFields: [],
}
); );
useEffect(() => { useEffect(() => {
fetchJobs(); fetchJobs();
@@ -123,6 +138,11 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
} }
}; };
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
<Card> <Card>
@@ -137,7 +157,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
@@ -146,11 +166,11 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
}, },
{ {
name: i18n._(t`Label Name`), name: i18n._(t`Label Name`),
key: 'labels__name', key: 'labels__name__icontains',
}, },
{ {
name: i18n._(t`Job Type`), name: i18n._(t`Job Type`),
key: `type`, key: `or__type`,
options: [ options: [
[`project_update`, i18n._(t`Source Control Update`)], [`project_update`, i18n._(t`Source Control Update`)],
[`inventory_update`, i18n._(t`Inventory Sync`)], [`inventory_update`, i18n._(t`Inventory Sync`)],
@@ -162,7 +182,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
}, },
{ {
name: i18n._(t`Launched By (Username)`), name: i18n._(t`Launched By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Status`), name: i18n._(t`Status`),
@@ -209,6 +229,8 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
key: 'started', key: 'started',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}

View File

@@ -96,6 +96,16 @@ UnifiedJobsAPI.read.mockResolvedValue({
data: { count: 3, results: mockResults }, data: { count: 3, results: mockResults },
}); });
UnifiedJobsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
function waitForLoaded(wrapper) { function waitForLoaded(wrapper) {
return waitForElement( return waitForElement(
wrapper, wrapper,

View File

@@ -52,7 +52,7 @@ function CredentialsStep({ i18n }) {
}, [fetchTypes]); }, [fetchTypes]);
const { const {
result: { credentials, count }, result: { credentials, count, actions, relatedSearchFields },
error: credentialsError, error: credentialsError,
isLoading: isCredentialsLoading, isLoading: isCredentialsLoading,
request: fetchCredentials, request: fetchCredentials,
@@ -62,16 +62,23 @@ function CredentialsStep({ i18n }) {
return { credentials: [], count: 0 }; return { credentials: [], count: 0 };
} }
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await CredentialsAPI.read({ const [{ data }, actionsResponse] = await Promise.all([
...params, CredentialsAPI.read({
credential_type: selectedType.id, ...params,
}); credential_type: selectedType.id,
}),
CredentialsAPI.readOptions(),
]);
return { return {
credentials: data.results, credentials: data.results,
count: data.count, count: data.count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [selectedType, history.location.search]), }, [selectedType, history.location.search]),
{ credentials: [], count: 0 } { credentials: [], count: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
@@ -97,6 +104,11 @@ function CredentialsStep({ i18n }) {
/> />
); );
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
{types && types.length > 0 && ( {types && types.length > 0 && (
@@ -129,16 +141,16 @@ function CredentialsStep({ i18n }) {
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -147,6 +159,8 @@ function CredentialsStep({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
multiple={isVault} multiple={isVault}
header={i18n._(t`Credentials`)} header={i18n._(t`Credentials`)}
name="credentials" name="credentials"

View File

@@ -31,6 +31,15 @@ describe('CredentialsStep', () => {
count: 5, count: 5,
}, },
}); });
CredentialsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
}); });
test('should load credentials', async () => { test('should load credentials', async () => {

View File

@@ -27,20 +27,29 @@ function InventoryStep({ i18n }) {
const { const {
isLoading, isLoading,
error, error,
result: { inventories, count }, result: { inventories, count, actions, relatedSearchFields },
request: fetchInventories, request: fetchInventories,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await InventoriesAPI.read(params); const [{ data }, actionsResponse] = await Promise.all([
InventoriesAPI.read(params),
InventoriesAPI.readOptions(),
]);
return { return {
inventories: data.results, inventories: data.results,
count: data.count, count: data.count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [history.location]), }, [history.location]),
{ {
count: 0, count: 0,
inventories: [], inventories: [],
actions: {},
relatedSearchFields: [],
} }
); );
@@ -48,6 +57,11 @@ function InventoryStep({ i18n }) {
fetchInventories(); fetchInventories();
}, [fetchInventories]); }, [fetchInventories]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
if (isLoading) { if (isLoading) {
return <ContentLoading />; return <ContentLoading />;
} }
@@ -63,16 +77,16 @@ function InventoryStep({ i18n }) {
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -81,6 +95,8 @@ function InventoryStep({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
header={i18n._(t`Inventory`)} header={i18n._(t`Inventory`)}
name="inventory" name="inventory"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}

View File

@@ -21,6 +21,16 @@ describe('InventoryStep', () => {
count: 3, count: 3,
}, },
}); });
InventoriesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
}); });
test('should load inventories', async () => { test('should load inventories', async () => {

View File

@@ -22,22 +22,41 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
const location = useLocation(); const location = useLocation();
const { const {
error, error,
result: { applications, itemCount }, result: { applications, itemCount, actions, relatedSearchFields },
request: fetchApplications, request: fetchApplications,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const { const [
data: { results, count }, {
} = await ApplicationsAPI.read(params); data: { results, count },
return { applications: results, itemCount: count }; },
actionsResponse,
] = await Promise.all([
ApplicationsAPI.read(params),
ApplicationsAPI.readOptions,
]);
return {
applications: results,
itemCount: count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, [location]), }, [location]),
{ applications: [], itemCount: 0 } { applications: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
fetchApplications(); fetchApplications();
}, [fetchApplications]); }, [fetchApplications]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<FormGroup fieldId="application" label={label}> <FormGroup fieldId="application" label={label}>
<Lookup <Lookup
@@ -56,12 +75,12 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Description`), name: i18n._(t`Description`),
key: 'description', key: 'description__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -82,6 +101,8 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
key: 'description', key: 'description',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
readOnly={!canDelete} readOnly={!canDelete}
name="application" name="application"
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}

View File

@@ -35,7 +35,7 @@ function CredentialLookup({
tooltip, tooltip,
}) { }) {
const { const {
result: { count, credentials }, result: { count, credentials, actions, relatedSearchFields },
error, error,
request: fetchCredentials, request: fetchCredentials,
} = useRequest( } = useRequest(
@@ -51,16 +51,23 @@ function CredentialLookup({
? { credential_type__namespace: credentialTypeNamespace } ? { credential_type__namespace: credentialTypeNamespace }
: {}; : {};
const { data } = await CredentialsAPI.read( const [{ data }, actionsResponse] = await Promise.all([
mergeParams(params, { CredentialsAPI.read(
...typeIdParams, mergeParams(params, {
...typeKindParams, ...typeIdParams,
...typeNamespaceParams, ...typeKindParams,
}) ...typeNamespaceParams,
); })
),
CredentialsAPI.readOptions,
]);
return { return {
count: data.count, count: data.count,
credentials: data.results, credentials: data.results,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [ }, [
credentialTypeId, credentialTypeId,
@@ -71,6 +78,8 @@ function CredentialLookup({
{ {
count: 0, count: 0,
credentials: [], credentials: [],
actions: {},
relatedSearchFields: [],
} }
); );
@@ -78,6 +87,11 @@ function CredentialLookup({
fetchCredentials(); fetchCredentials();
}, [fetchCredentials]); }, [fetchCredentials]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
// TODO: replace credential type search with REST-based grabbing of cred types // TODO: replace credential type search with REST-based grabbing of cred types
return ( return (
@@ -107,16 +121,16 @@ function CredentialLookup({
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -125,6 +139,8 @@ function CredentialLookup({
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
readOnly={!canDelete} readOnly={!canDelete}
name="credential" name="credential"
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })} selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}

View File

@@ -19,26 +19,38 @@ const QS_CONFIG = getQSConfig('inventory', {
function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) { function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
const { const {
result: { inventories, count }, result: { inventories, count, actions, relatedSearchFields },
request: fetchInventories, request: fetchInventories,
error, error,
isLoading, isLoading,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await InventoriesAPI.read(params); const [{ data }, actionsResponse] = await Promise.all([
InventoriesAPI.read(params),
InventoriesAPI.readOptions(),
]);
return { return {
inventories: data.results, inventories: data.results,
count: data.count, count: data.count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [history.location]), }, [history.location]),
{ inventories: [], count: 0 } { inventories: [], count: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
fetchInventories(); fetchInventories();
}, [fetchInventories]); }, [fetchInventories]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
<Lookup <Lookup
@@ -58,16 +70,16 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -76,6 +88,8 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
multiple={state.multiple} multiple={state.multiple}
header={i18n._(t`Inventory`)} header={i18n._(t`Inventory`)}
name="inventory" name="inventory"

View File

@@ -0,0 +1,156 @@
import React, { useCallback, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { func, bool, number, node, string, oneOfType } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { FormGroup } from '@patternfly/react-core';
import Lookup from './Lookup';
import LookupErrorMessage from './shared/LookupErrorMessage';
import OptionsList from '../OptionsList';
import { InventoriesAPI, InventoryScriptsAPI } from '../../api';
import { InventoryScript } from '../../types';
import useRequest from '../../util/useRequest';
import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs';
const QS_CONFIG = getQSConfig('inventory_scripts', {
order_by: 'name',
page: 1,
page_size: 5,
role_level: 'admin_role',
});
function InventoryScriptLookup({
helperTextInvalid,
history,
i18n,
inventoryId,
isValid,
onBlur,
onChange,
required,
value,
}) {
const {
result: { count, inventoryScripts, actions, relatedSearchFields },
error,
request: fetchInventoryScripts,
} = useRequest(
useCallback(async () => {
const parsedParams = parseQueryString(QS_CONFIG, history.location.search);
const [
{
data: { organization },
},
actionsResponse,
] = await Promise.all([
InventoriesAPI.readDetail(inventoryId),
InventoriesAPI.readOptions(),
]);
const { data } = await InventoryScriptsAPI.read(
mergeParams(parsedParams, { organization })
);
return {
count: data.count,
inventoryScripts: data.results,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, [history.location.search, inventoryId]),
{
count: 0,
inventoryScripts: [],
actions: {},
relatedSearchFields: [],
}
);
useEffect(() => {
fetchInventoryScripts();
}, [fetchInventoryScripts]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return (
<FormGroup
fieldId="inventory-script"
helperTextInvalid={helperTextInvalid}
isRequired={required}
validated={isValid ? 'default' : 'error'}
label={i18n._(t`Inventory script`)}
>
<Lookup
id="inventory-script-lookup"
header={i18n._(t`Inventory script`)}
value={value}
onChange={onChange}
onBlur={onBlur}
required={required}
qsConfig={QS_CONFIG}
renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList
header={i18n._(t`Inventory script`)}
multiple={state.multiple}
name="inventory-script"
optionCount={count}
options={inventoryScripts}
qsConfig={QS_CONFIG}
readOnly={!canDelete}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
value={state.selectedItems}
searchColumns={[
{
name: i18n._(t`Name`),
key: 'name__icontains',
isDefault: true,
},
{
name: i18n._(t`Created By (Username)`),
key: 'created_by__username__icontains',
},
{
name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username__icontains',
},
]}
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
)}
/>
<LookupErrorMessage error={error} />
</FormGroup>
);
}
InventoryScriptLookup.propTypes = {
helperTextInvalid: node,
inventoryId: oneOfType([number, string]).isRequired,
isValid: bool,
onBlur: func,
onChange: func.isRequired,
required: bool,
value: InventoryScript,
};
InventoryScriptLookup.defaultProps = {
helperTextInvalid: '',
isValid: true,
onBlur: () => {},
required: false,
value: null,
};
export default withI18n()(withRouter(InventoryScriptLookup));

View File

@@ -49,7 +49,7 @@ function MultiCredentialsLookup(props) {
}, [fetchTypes]); }, [fetchTypes]);
const { const {
result: { credentials, credentialsCount }, result: { credentials, credentialsCount, actions, relatedSearchFields },
request: fetchCredentials, request: fetchCredentials,
error: credentialsError, error: credentialsError,
isLoading: isCredentialsLoading, isLoading: isCredentialsLoading,
@@ -62,15 +62,24 @@ function MultiCredentialsLookup(props) {
}; };
} }
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { results, count } = await loadCredentials(params, selectedType.id); const [{ results, count }, actionsResponse] = await Promise.all([
loadCredentials(params, selectedType.id),
CredentialsAPI.readOptions(),
]);
return { return {
credentials: results, credentials: results,
credentialsCount: count, credentialsCount: count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [selectedType, history.location]), }, [selectedType, history.location]),
{ {
credentials: [], credentials: [],
credentialsCount: 0, credentialsCount: 0,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -95,6 +104,11 @@ function MultiCredentialsLookup(props) {
const isVault = selectedType?.kind === 'vault'; const isVault = selectedType?.kind === 'vault';
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<Lookup <Lookup
id="multiCredential" id="multiCredential"
@@ -149,16 +163,16 @@ function MultiCredentialsLookup(props) {
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -167,6 +181,8 @@ function MultiCredentialsLookup(props) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
multiple={isVault} multiple={isVault}
header={i18n._(t`Credentials`)} header={i18n._(t`Credentials`)}
name="credentials" name="credentials"

View File

@@ -43,6 +43,15 @@ describe('<MultiCredentialsLookup />', () => {
count: 3, count: 3,
}, },
}); });
CredentialsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
}); });
afterEach(() => { afterEach(() => {

View File

@@ -80,16 +80,16 @@ function OrganizationLookup({
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[

View File

@@ -32,25 +32,34 @@ function ProjectLookup({
history, history,
}) { }) {
const { const {
result: { projects, count }, result: { projects, count, actions, relatedSearchFields },
request: fetchProjects, request: fetchProjects,
error, error,
isLoading, isLoading,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await ProjectsAPI.read(params); const [{ data }, actionsResponse] = await Promise.all([
ProjectsAPI.read(params),
ProjectsAPI.readOptions(),
]);
if (data.count === 1 && autocomplete) { if (data.count === 1 && autocomplete) {
autocomplete(data.results[0]); autocomplete(data.results[0]);
} }
return { return {
count: data.count, count: data.count,
projects: data.results, projects: data.results,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [history.location.search, autocomplete]), }, [history.location.search, autocomplete]),
{ {
count: 0, count: 0,
projects: [], projects: [],
actions: {},
relatedSearchFields: [],
} }
); );
@@ -58,6 +67,11 @@ function ProjectLookup({
fetchProjects(); fetchProjects();
}, [fetchProjects]); }, [fetchProjects]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<FormGroup <FormGroup
fieldId="project" fieldId="project"
@@ -83,12 +97,12 @@ function ProjectLookup({
searchColumns={[ searchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'scm_type', key: 'or__scm_type',
options: [ options: [
[``, i18n._(t`Manual`)], [``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)], [`git`, i18n._(t`Git`)],
@@ -99,15 +113,15 @@ function ProjectLookup({
}, },
{ {
name: i18n._(t`Source Control URL`), name: i18n._(t`Source Control URL`),
key: 'scm_url', key: 'scm_url__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
@@ -116,6 +130,8 @@ function ProjectLookup({
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
options={projects} options={projects}
optionCount={count} optionCount={count}
multiple={state.multiple} multiple={state.multiple}

View File

@@ -145,12 +145,12 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'type', key: 'or__type',
options: [ options: [
['email', i18n._(t`Email`)], ['email', i18n._(t`Email`)],
['grafana', i18n._(t`Grafana`)], ['grafana', i18n._(t`Grafana`)],
@@ -165,12 +165,12 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
], ],
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -30,6 +30,8 @@ function OptionsList({
optionCount, optionCount,
searchColumns, searchColumns,
sortColumns, sortColumns,
searchableKeys,
relatedSearchableKeys,
multiple, multiple,
header, header,
name, name,
@@ -61,6 +63,8 @@ function OptionsList({
qsConfig={qsConfig} qsConfig={qsConfig}
toolbarSearchColumns={searchColumns} toolbarSearchColumns={searchColumns}
toolbarSortColumns={sortColumns} toolbarSortColumns={sortColumns}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
hasContentLoading={isLoading} hasContentLoading={isLoading}
onRowClick={selectItem} onRowClick={selectItem}
renderItem={item => ( renderItem={item => (

View File

@@ -17,7 +17,9 @@ describe('<OptionsList />', () => {
value={[]} value={[]}
options={options} options={options}
optionCount={3} optionCount={3}
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]} searchColumns={[
{ name: 'Foo', key: 'foo__icontains', isDefault: true },
]}
sortColumns={[{ name: 'Foo', key: 'foo' }]} sortColumns={[{ name: 'Foo', key: 'foo' }]}
qsConfig={qsConfig} qsConfig={qsConfig}
selectItem={() => {}} selectItem={() => {}}
@@ -40,7 +42,9 @@ describe('<OptionsList />', () => {
value={[options[1]]} value={[options[1]]}
options={options} options={options}
optionCount={3} optionCount={3}
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]} searchColumns={[
{ name: 'Foo', key: 'foo__icontains', isDefault: true },
]}
sortColumns={[{ name: 'Foo', key: 'foo' }]} sortColumns={[{ name: 'Foo', key: 'foo' }]}
qsConfig={qsConfig} qsConfig={qsConfig}
selectItem={() => {}} selectItem={() => {}}

View File

@@ -80,16 +80,16 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Username`), name: i18n._(t`Username`),
key: 'username', key: 'username__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`First name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name__icontains',
}, },
{ {
name: i18n._(t`Last name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -98,11 +98,11 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
key: 'username', key: 'username',
}, },
{ {
name: i18n._(t`First name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name',
}, },
{ {
name: i18n._(t`Last name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name',
}, },
]} ]}

View File

@@ -32,7 +32,7 @@ function ScheduleList({
const location = useLocation(); const location = useLocation();
const { const {
result: { schedules, itemCount, actions }, result: { schedules, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchSchedules, request: fetchSchedules,
@@ -49,12 +49,16 @@ function ScheduleList({
schedules: results, schedules: results,
itemCount: count, itemCount: count,
actions: scheduleActions.data.actions, actions: scheduleActions.data.actions,
relatedSearchFields: (
scheduleActions?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location, loadSchedules, loadScheduleOptions]), }, [location, loadSchedules, loadScheduleOptions]),
{ {
schedules: [], schedules: [],
itemCount: 0, itemCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -102,6 +106,10 @@ function ScheduleList({
actions && actions &&
Object.prototype.hasOwnProperty.call(actions, 'POST') && Object.prototype.hasOwnProperty.call(actions, 'POST') &&
!hideAddButton; !hideAddButton;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
@@ -123,7 +131,7 @@ function ScheduleList({
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
]} ]}
@@ -141,6 +149,8 @@ function ScheduleList({
key: 'unified_job_template__polymorphic_ctype__model', key: 'unified_job_template__polymorphic_ctype__model',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -82,7 +82,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
fetchItems: JobTemplatesAPI.read, fetchItems: JobTemplatesAPI.read,
label: 'Job template', label: 'Job template',
selectedResource: 'jobTemplate', selectedResource: 'jobTemplate',
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }], searchColumns: [
{ name: 'Name', key: 'name__icontains', isDefault: true },
],
sortColumns: [{ name: 'Name', key: 'name' }], sortColumns: [{ name: 'Name', key: 'name' }],
}) })
); );
@@ -116,7 +118,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
fetchItems: JobTemplatesAPI.read, fetchItems: JobTemplatesAPI.read,
label: 'Job template', label: 'Job template',
selectedResource: 'jobTemplate', selectedResource: 'jobTemplate',
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }], searchColumns: [
{ name: 'Name', key: 'name__icontains', isDefault: true },
],
sortColumns: [{ name: 'Name', key: 'name' }], sortColumns: [{ name: 'Name', key: 'name' }],
}) })
); );
@@ -190,7 +194,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
fetchItems: JobTemplatesAPI.read, fetchItems: JobTemplatesAPI.read,
label: 'Job template', label: 'Job template',
selectedResource: 'jobTemplate', selectedResource: 'jobTemplate',
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }], searchColumns: [
{ name: 'Name', key: 'name__icontains', isDefault: true },
],
sortColumns: [{ name: 'Name', key: 'name' }], sortColumns: [{ name: 'Name', key: 'name' }],
}) })
); );

View File

@@ -16,20 +16,20 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Playbook name`), name: i18n._(t`Playbook name`),
key: 'playbook', key: 'playbook__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [
@@ -46,20 +46,20 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Playbook name`), name: i18n._(t`Playbook name`),
key: 'playbook', key: 'playbook__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [
@@ -76,12 +76,12 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'scm_type', key: 'or__scm_type',
options: [ options: [
[``, i18n._(t`Manual`)], [``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)], [`git`, i18n._(t`Git`)],
@@ -92,15 +92,15 @@ export default function getResourceAccessConfig(i18n) {
}, },
{ {
name: i18n._(t`Source Control URL`), name: i18n._(t`Source Control URL`),
key: 'scm_url', key: 'scm_url__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [
@@ -117,16 +117,16 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [
@@ -143,12 +143,12 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'scm_type', key: 'or__scm_type',
options: [ options: [
[``, i18n._(t`Manual`)], [``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)], [`git`, i18n._(t`Git`)],
@@ -159,15 +159,15 @@ export default function getResourceAccessConfig(i18n) {
}, },
{ {
name: i18n._(t`Source Control URL`), name: i18n._(t`Source Control URL`),
key: 'scm_url', key: 'scm_url__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [
@@ -184,16 +184,16 @@ export default function getResourceAccessConfig(i18n) {
searchColumns: [ searchColumns: [
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
], ],
sortColumns: [ sortColumns: [

View File

@@ -26,14 +26,20 @@ function ApplicationTokenList({ i18n }) {
const { const {
error, error,
isLoading, isLoading,
result: { tokens, itemCount }, result: { tokens, itemCount, actions, relatedSearchFields },
request: fetchTokens, request: fetchTokens,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const { const [
data: { results, count }, {
} = await ApplicationsAPI.readTokens(id, params); data: { results, count },
},
actionsResponse,
] = await Promise.all([
ApplicationsAPI.readTokens(id, params),
ApplicationsAPI.readTokenOptions(id),
]);
const modifiedResults = results.map(result => { const modifiedResults = results.map(result => {
result.summary_fields = { result.summary_fields = {
user: result.summary_fields.user, user: result.summary_fields.user,
@@ -43,9 +49,16 @@ function ApplicationTokenList({ i18n }) {
result.name = result.summary_fields.user?.username; result.name = result.summary_fields.user?.username;
return result; return result;
}); });
return { tokens: modifiedResults, itemCount: count }; return {
tokens: modifiedResults,
itemCount: count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, [id, location.search]), }, [id, location.search]),
{ tokens: [], itemCount: 0 } { tokens: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
@@ -77,6 +90,12 @@ function ApplicationTokenList({ i18n }) {
await handleDeleteApplications(); await handleDeleteApplications();
setSelected([]); setSelected([]);
}; };
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
<PaginatedDataList <PaginatedDataList
@@ -90,7 +109,7 @@ function ApplicationTokenList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'user__username', key: 'user__username__icontains',
isDefault: true, isDefault: true,
}, },
]} ]}
@@ -116,6 +135,8 @@ function ApplicationTokenList({ i18n }) {
key: 'modified', key: 'modified',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}

View File

@@ -82,6 +82,19 @@ const tokens = {
}; };
describe('<ApplicationTokenList/>', () => { describe('<ApplicationTokenList/>', () => {
let wrapper; let wrapper;
beforeEach(() => {
ApplicationsAPI.readTokenOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
});
test('should mount properly', async () => { test('should mount properly', async () => {
ApplicationsAPI.readTokens.mockResolvedValue(tokens); ApplicationsAPI.readTokens.mockResolvedValue(tokens);
await act(async () => { await act(async () => {

View File

@@ -32,7 +32,7 @@ function ApplicationsList({ i18n }) {
isLoading, isLoading,
error, error,
request: fetchApplications, request: fetchApplications,
result: { applications, itemCount, actions }, result: { applications, itemCount, actions, relatedSearchFields },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
@@ -46,12 +46,16 @@ function ApplicationsList({ i18n }) {
applications: response.data.results, applications: response.data.results,
itemCount: response.data.count, itemCount: response.data.count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
applications: [], applications: [],
itemCount: 0, itemCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -85,6 +89,10 @@ function ApplicationsList({ i18n }) {
}; };
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
@@ -101,12 +109,12 @@ function ApplicationsList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Description`), name: i18n._(t`Description`),
key: 'description', key: 'description__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -127,6 +135,8 @@ function ApplicationsList({ i18n }) {
key: 'description', key: 'description',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}

View File

@@ -20,6 +20,16 @@ CredentialsAPI.read.mockResolvedValue({
}, },
}); });
CredentialsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
CredentialTypesAPI.readDetail.mockResolvedValue({ CredentialTypesAPI.readDetail.mockResolvedValue({
data: { data: {
id: 20, id: 20,

View File

@@ -25,28 +25,38 @@ function CredentialsStep({ i18n }) {
const history = useHistory(); const history = useHistory();
const { const {
result: { credentials, count }, result: { credentials, count, actions, relatedSearchFields },
error: credentialsError, error: credentialsError,
isLoading: isCredentialsLoading, isLoading: isCredentialsLoading,
request: fetchCredentials, request: fetchCredentials,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search); const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await CredentialsAPI.read({ const [{ data }, actionsResponse] = await Promise.all([
...params, CredentialsAPI.read({ ...params }),
}); CredentialsAPI.readOptions(),
]);
return { return {
credentials: data.results, credentials: data.results,
count: data.count, count: data.count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [history.location.search]), }, [history.location.search]),
{ credentials: [], count: 0 } { credentials: [], count: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
fetchCredentials(); fetchCredentials();
}, [fetchCredentials]); }, [fetchCredentials]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
if (credentialsError) { if (credentialsError) {
return <ContentError error={credentialsError} />; return <ContentError error={credentialsError} />;
} }
@@ -76,16 +86,16 @@ function CredentialsStep({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -94,6 +104,8 @@ function CredentialsStep({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/> />
); );
} }

View File

@@ -33,7 +33,7 @@ function HostGroupsList({ i18n, host }) {
const invId = host.summary_fields.inventory.id; const invId = host.summary_fields.inventory.id;
const { const {
result: { groups, itemCount, actions }, result: { groups, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchGroups, request: fetchGroups,
@@ -55,11 +55,16 @@ function HostGroupsList({ i18n, host }) {
groups: results, groups: results,
itemCount: count, itemCount: count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [hostId, search]), }, [hostId, search]),
{ {
groups: [], groups: [],
itemCount: 0, itemCount: 0,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -123,6 +128,10 @@ function HostGroupsList({ i18n, host }) {
const canAdd = const canAdd =
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
@@ -136,16 +145,16 @@ function HostGroupsList({ i18n, host }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -154,6 +163,8 @@ function HostGroupsList({ i18n, host }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={item => ( renderItem={item => (
<HostGroupItem <HostGroupItem
key={item.id} key={item.id}

View File

@@ -29,7 +29,7 @@ function HostList({ i18n }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const { const {
result: { hosts, count, actions }, result: { hosts, count, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchHosts, request: fetchHosts,
@@ -44,12 +44,16 @@ function HostList({ i18n }) {
hosts: results[0].data.results, hosts: results[0].data.results,
count: results[0].data.count, count: results[0].data.count,
actions: results[1].data.actions, actions: results[1].data.actions,
relatedSearchFields: (
results[1]?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
hosts: [], hosts: [],
count: 0, count: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -93,6 +97,10 @@ function HostList({ i18n }) {
const canAdd = const canAdd =
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<PageSection> <PageSection>
@@ -108,16 +116,16 @@ function HostList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -126,6 +134,8 @@ function HostList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -32,7 +32,7 @@ function InventoryGroupHostList({ i18n }) {
const history = useHistory(); const history = useHistory();
const { const {
result: { hosts, hostCount, actions }, result: { hosts, hostCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchHosts, request: fetchHosts,
@@ -48,11 +48,16 @@ function InventoryGroupHostList({ i18n }) {
hosts: response.data.results, hosts: response.data.results,
hostCount: response.data.count, hostCount: response.data.count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [groupId, inventoryId, location.search]), }, [groupId, inventoryId, location.search]),
{ {
hosts: [], hosts: [],
hostCount: 0, hostCount: 0,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -122,6 +127,10 @@ function InventoryGroupHostList({ i18n }) {
const canAdd = const canAdd =
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`; const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
@@ -136,16 +145,16 @@ function InventoryGroupHostList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -154,6 +163,8 @@ function InventoryGroupHostList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -141,7 +141,7 @@ function InventoryGroupsList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
@@ -154,12 +154,12 @@ function InventoryGroupsList({ i18n }) {
}, },
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -31,7 +31,7 @@ function InventoryHostGroupsList({ i18n }) {
const { search } = useLocation(); const { search } = useLocation();
const { const {
result: { groups, itemCount, actions }, result: { groups, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchGroups, request: fetchGroups,
@@ -53,11 +53,16 @@ function InventoryHostGroupsList({ i18n }) {
groups: results, groups: results,
itemCount: count, itemCount: count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [hostId, search]), // eslint-disable-line react-hooks/exhaustive-deps }, [hostId, search]), // eslint-disable-line react-hooks/exhaustive-deps
{ {
groups: [], groups: [],
itemCount: 0, itemCount: 0,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -121,6 +126,10 @@ function InventoryHostGroupsList({ i18n }) {
const canAdd = const canAdd =
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
@@ -134,16 +143,16 @@ function InventoryHostGroupsList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -152,6 +161,8 @@ function InventoryHostGroupsList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={item => ( renderItem={item => (
<InventoryHostGroupItem <InventoryHostGroupItem
key={item.id} key={item.id}

View File

@@ -29,7 +29,7 @@ function InventoryList({ i18n }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const { const {
result: { results, itemCount, actions }, result: { results, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchInventories, request: fetchInventories,
@@ -44,12 +44,16 @@ function InventoryList({ i18n }) {
results: response.data.results, results: response.data.results,
itemCount: response.data.count, itemCount: response.data.count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
results: [], results: [],
itemCount: 0, itemCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -93,6 +97,10 @@ function InventoryList({ i18n }) {
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const handleSelectAll = isSelected => { const handleSelectAll = isSelected => {
setSelected(isSelected ? [...inventories] : []); setSelected(isSelected ? [...inventories] : []);
@@ -135,16 +143,16 @@ function InventoryList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -153,6 +161,8 @@ function InventoryList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}

View File

@@ -31,7 +31,7 @@ function OrganizationsList({ i18n }) {
const addUrl = `${match.url}/add`; const addUrl = `${match.url}/add`;
const { const {
result: { organizations, organizationCount, actions }, result: { organizations, organizationCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading: isOrgsLoading, isLoading: isOrgsLoading,
request: fetchOrganizations, request: fetchOrganizations,
@@ -46,12 +46,16 @@ function OrganizationsList({ i18n }) {
organizations: orgs.data.results, organizations: orgs.data.results,
organizationCount: orgs.data.count, organizationCount: orgs.data.count,
actions: orgActions.data.actions, actions: orgActions.data.actions,
relatedSearchFields: (
orgActions?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
organizations: [], organizations: [],
organizationCount: 0, organizationCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -86,6 +90,10 @@ function OrganizationsList({ i18n }) {
const hasContentLoading = isDeleteLoading || isOrgsLoading; const hasContentLoading = isDeleteLoading || isOrgsLoading;
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const handleSelectAll = isSelected => { const handleSelectAll = isSelected => {
setSelected(isSelected ? [...organizations] : []); setSelected(isSelected ? [...organizations] : []);
@@ -114,16 +122,16 @@ function OrganizationsList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -132,6 +140,8 @@ function OrganizationsList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -53,16 +53,16 @@ function OrganizationTeamList({ id, i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created by (username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified by (username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -103,21 +103,12 @@ function ProjectJobTemplatesList({ i18n }) {
onRowClick={handleSelect} onRowClick={handleSelect}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Created By (Username)`),
key: 'name', key: 'created_by__username__icontains',
isDefault: true,
}, },
{ {
name: i18n._(t`Playbook name`), name: i18n._(t`Modified By (Username)`),
key: 'job_template__playbook', key: 'modified_by__username__icontains',
},
{
name: i18n._(t`Created by (username)`),
key: 'created_by__username',
},
{
name: i18n._(t`Modified by (username)`),
key: 'modified_by__username',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -30,7 +30,7 @@ function ProjectList({ i18n }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const { const {
result: { results, itemCount, actions }, result: { results, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchProjects, request: fetchProjects,
@@ -45,12 +45,16 @@ function ProjectList({ i18n }) {
results: response.data.results, results: response.data.results,
itemCount: response.data.count, itemCount: response.data.count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
results: [], results: [],
itemCount: 0, itemCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -85,6 +89,10 @@ function ProjectList({ i18n }) {
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const handleSelectAll = isSelected => { const handleSelectAll = isSelected => {
setSelected(isSelected ? [...projects] : []); setSelected(isSelected ? [...projects] : []);
@@ -113,12 +121,12 @@ function ProjectList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'scm_type', key: 'or__scm_type',
options: [ options: [
[``, i18n._(t`Manual`)], [``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)], [`git`, i18n._(t`Git`)],
@@ -129,17 +137,19 @@ function ProjectList({ i18n }) {
}, },
{ {
name: i18n._(t`Source Control URL`), name: i18n._(t`Source Control URL`),
key: 'scm_url', key: 'scm_url__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSortColumns={[ toolbarSortColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),

View File

@@ -78,6 +78,7 @@ describe('<ProjectList />', () => {
GET: {}, GET: {},
POST: {}, POST: {},
}, },
related_search_fields: [],
}, },
}); });
}); });

View File

@@ -29,7 +29,7 @@ function TeamList({ i18n }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const { const {
result: { teams, itemCount, actions }, result: { teams, itemCount, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchTeams, request: fetchTeams,
@@ -44,12 +44,16 @@ function TeamList({ i18n }) {
teams: response.data.results, teams: response.data.results,
itemCount: response.data.count, itemCount: response.data.count,
actions: actionsResponse.data.actions, actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [location]), }, [location]),
{ {
teams: [], teams: [],
itemCount: 0, itemCount: 0,
actions: {}, actions: {},
relatedSearchFields: [],
} }
); );
@@ -81,6 +85,10 @@ function TeamList({ i18n }) {
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const handleSelectAll = isSelected => { const handleSelectAll = isSelected => {
setSelected(isSelected ? [...teams] : []); setSelected(isSelected ? [...teams] : []);
@@ -109,20 +117,20 @@ function TeamList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Organization Name`), name: i18n._(t`Organization Name`),
key: 'organization__name', key: 'organization__name__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -131,6 +139,8 @@ function TeamList({ i18n }) {
key: 'name', key: 'name',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -37,7 +37,7 @@ function TeamRolesList({ i18n, me, team }) {
isLoading, isLoading,
request: fetchRoles, request: fetchRoles,
contentError, contentError,
result: { roleCount, roles, isAdminOfOrg }, result: { roleCount, roles, isAdminOfOrg, actions, relatedSearchFields },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, search); const params = parseQueryString(QS_CONFIG, search);
@@ -46,22 +46,30 @@ function TeamRolesList({ i18n, me, team }) {
data: { results, count }, data: { results, count },
}, },
{ count: orgAdminCount }, { count: orgAdminCount },
actionsResponse,
] = await Promise.all([ ] = await Promise.all([
TeamsAPI.readRoles(team.id, params), TeamsAPI.readRoles(team.id, params),
UsersAPI.readAdminOfOrganizations(me.id, { UsersAPI.readAdminOfOrganizations(me.id, {
id: team.organization, id: team.organization,
}), }),
TeamsAPI.readRoleOptions(team.id),
]); ]);
return { return {
roleCount: count, roleCount: count,
roles: results, roles: results,
isAdminOfOrg: orgAdminCount > 0, isAdminOfOrg: orgAdminCount > 0,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [me.id, team.id, team.organization, search]), }, [me.id, team.id, team.organization, search]),
{ {
roles: [], roles: [],
roleCount: 0, roleCount: 0,
isAdminOfOrg: false, isAdminOfOrg: false,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -90,6 +98,10 @@ function TeamRolesList({ i18n, me, team }) {
); );
const canAdd = team?.summary_fields?.user_capabilities?.edit || isAdminOfOrg; const canAdd = team?.summary_fields?.user_capabilities?.edit || isAdminOfOrg;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const detailUrl = role => { const detailUrl = role => {
const { resource_id, resource_type } = role.summary_fields; const { resource_id, resource_type } = role.summary_fields;
@@ -136,16 +148,18 @@ function TeamRolesList({ i18n, me, team }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Role`), name: i18n._(t`Role`),
key: 'role_field', key: 'role_field__icontains',
isDefault: true, isDefault: true,
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`ID`),
key: 'id', key: 'id',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DataListToolbar <DataListToolbar
{...props} {...props}

View File

@@ -164,6 +164,13 @@ describe('<TeamRolesList />', () => {
}, },
], ],
}); });
TeamsAPI.readRoleOptions.mockResolvedValue({
data: {
actions: { GET: {} },
related_search_fields: [],
},
});
}); });
afterEach(() => { afterEach(() => {

View File

@@ -36,7 +36,7 @@ function TemplateList({ i18n }) {
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const { const {
result: { results, count, jtActions, wfjtActions }, result: { results, count, jtActions, wfjtActions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchTemplates, request: fetchTemplates,
@@ -53,6 +53,14 @@ function TemplateList({ i18n }) {
count: responses[0].data.count, count: responses[0].data.count,
jtActions: responses[1].data.actions, jtActions: responses[1].data.actions,
wfjtActions: responses[2].data.actions, wfjtActions: responses[2].data.actions,
relatedSearchFields: [
...(responses[1]?.data?.related_search_fields || []).map(val =>
val.slice(0, -8)
),
...(responses[2]?.data?.related_search_fields || []).map(val =>
val.slice(0, -8)
),
],
}; };
}, [location]), }, [location]),
{ {
@@ -60,6 +68,7 @@ function TemplateList({ i18n }) {
count: 0, count: 0,
jtActions: {}, jtActions: {},
wfjtActions: {}, wfjtActions: {},
relatedSearchFields: [],
} }
); );
@@ -118,6 +127,18 @@ function TemplateList({ i18n }) {
jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST'); jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
const canAddWFJT = const canAddWFJT =
wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST'); wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST');
// spreading Set() returns only unique keys
const relatedSearchableKeys = [...new Set(relatedSearchFields)] || [];
const searchableKeys = [
...new Set([
...Object.keys(jtActions?.GET || {}).filter(
key => jtActions.GET[key].filterable
),
...Object.keys(wfjtActions?.GET || {}).filter(
key => wfjtActions.GET[key].filterable
),
]),
];
const addButtonOptions = []; const addButtonOptions = [];
if (canAddJT) { if (canAddJT) {
@@ -152,16 +173,16 @@ function TemplateList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Description`), name: i18n._(t`Description`),
key: 'description', key: 'description__icontains',
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'type', key: 'or__type',
options: [ options: [
[`job_template`, i18n._(t`Job Template`)], [`job_template`, i18n._(t`Job Template`)],
[`workflow_job_template`, i18n._(t`Workflow Template`)], [`workflow_job_template`, i18n._(t`Workflow Template`)],
@@ -169,15 +190,15 @@ function TemplateList({ i18n }) {
}, },
{ {
name: i18n._(t`Playbook name`), name: i18n._(t`Playbook name`),
key: 'job_template__playbook', key: 'job_template__playbook__icontains',
}, },
{ {
name: i18n._(t`Created By (Username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified By (Username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -206,6 +227,8 @@ function TemplateList({ i18n }) {
key: 'type', key: 'type',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}

View File

@@ -68,12 +68,12 @@ function InventorySourcesList({ i18n, nodeResource, onUpdateNodeResource }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Source`), name: i18n._(t`Source`),
key: 'source', key: 'or__source',
options: [ options: [
[`file`, i18n._(t`File, directory or script`)], [`file`, i18n._(t`File, directory or script`)],
[`scm`, i18n._(t`Sourced from a project`)], [`scm`, i18n._(t`Sourced from a project`)],

View File

@@ -70,20 +70,20 @@ function JobTemplatesList({ i18n, nodeResource, onUpdateNodeResource }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Playbook name`), name: i18n._(t`Playbook name`),
key: 'playbook', key: 'playbook__icontains',
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -68,12 +68,12 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Type`), name: i18n._(t`Type`),
key: 'scm_type', key: 'or__scm_type',
options: [ options: [
[``, i18n._(t`Manual`)], [``, i18n._(t`Manual`)],
[`git`, i18n._(t`Git`)], [`git`, i18n._(t`Git`)],
@@ -83,16 +83,16 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
], ],
}, },
{ {
name: i18n._(t`Source control URL`), name: i18n._(t`Source Control URL`),
key: 'scm_url', key: 'scm_url__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -74,24 +74,24 @@ function WorkflowJobTemplatesList({
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Organization (name)`), name: i18n._(t`Organization (Name)`),
key: 'organization__name', key: 'organization__name__icontains',
}, },
{ {
name: i18n._(t`Inventory (name)`), name: i18n._(t`Inventory (Name)`),
key: 'inventory__name', key: 'inventory__name__icontains',
}, },
{ {
name: i18n._(t`Created by (username)`), name: i18n._(t`Created By (Username)`),
key: 'created_by__username', key: 'created_by__username__icontains',
}, },
{ {
name: i18n._(t`Modified by (username)`), name: i18n._(t`Modified By (Username)`),
key: 'modified_by__username', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -32,13 +32,13 @@ const QS_CONFIG = getQSConfig('roles', {
function UserAccessList({ i18n, user }) { function UserAccessList({ i18n, user }) {
const { search } = useLocation(); const { search } = useLocation();
const [isWizardOpen, setIsWizardOpen] = useState(false); const [isWizardOpen, setIsWizardOpen] = useState(false);
const [roleToDisassociate, setRoleToDisassociate] = useState(null); const [roleToDisassociate, setRoleToDisassociate] = useState(null);
const { const {
isLoading, isLoading,
request: fetchRoles, request: fetchRoles,
error, error,
result: { roleCount, roles, options }, result: { roleCount, roles, actions, relatedSearchFields },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, search); const params = parseQueryString(QS_CONFIG, search);
@@ -46,20 +46,28 @@ function UserAccessList({ i18n, user }) {
{ {
data: { results, count }, data: { results, count },
}, },
{ actionsResponse,
data: { actions },
},
] = await Promise.all([ ] = await Promise.all([
UsersAPI.readRoles(user.id, params), UsersAPI.readRoles(user.id, params),
UsersAPI.readOptions(), UsersAPI.readOptions(),
]); ]);
return { roleCount: count, roles: results, options: actions }; return {
roleCount: count,
roles: results,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, [user.id, search]), }, [user.id, search]),
{ {
roles: [], roles: [],
roleCount: 0, roleCount: 0,
actions: {},
relatedSearchFields: [],
} }
); );
useEffect(() => { useEffect(() => {
fetchRoles(); fetchRoles();
}, [fetchRoles]); }, [fetchRoles]);
@@ -82,7 +90,12 @@ function UserAccessList({ i18n, user }) {
const canAdd = const canAdd =
user?.summary_fields?.user_capabilities?.edit || user?.summary_fields?.user_capabilities?.edit ||
(options && Object.prototype.hasOwnProperty.call(options, 'POST')); (actions && Object.prototype.hasOwnProperty.call(actions, 'POST'));
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
const saveRoles = () => { const saveRoles = () => {
setIsWizardOpen(false); setIsWizardOpen(false);
@@ -132,16 +145,18 @@ function UserAccessList({ i18n, user }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Role`), name: i18n._(t`Role`),
key: 'role_field', key: 'role_field__icontains',
isDefault: true, isDefault: true,
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`ID`),
key: 'id', key: 'id',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={role => { renderItem={role => {
return ( return (
<UserAccessListItem <UserAccessListItem

View File

@@ -12,9 +12,8 @@ jest.mock('../../../api/models/Roles');
UsersAPI.readOptions.mockResolvedValue({ UsersAPI.readOptions.mockResolvedValue({
data: { data: {
actions: { actions: { GET: {} },
GET: {}, related_search_fields: [],
},
}, },
}); });
@@ -144,6 +143,15 @@ describe('<UserAccessList />', () => {
); );
}); });
test('should not render add button when user cannot create other users and user cannot edit this user', async () => { test('should not render add button when user cannot create other users and user cannot edit this user', async () => {
UsersAPI.readRoleOptions.mockResolvedValueOnce({
data: {
actions: {
GET: {},
},
related_search_fields: [],
},
});
UsersAPI.readRoles.mockResolvedValue({ UsersAPI.readRoles.mockResolvedValue({
data: { data: {
results: [ results: [

View File

@@ -98,16 +98,16 @@ function UserList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Username`), name: i18n._(t`Username`),
key: 'username', key: 'username__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`First name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name__icontains',
}, },
{ {
name: i18n._(t`Last name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
@@ -116,11 +116,11 @@ function UserList({ i18n }) {
key: 'username', key: 'username',
}, },
{ {
name: i18n._(t`First name`), name: i18n._(t`First Name`),
key: 'first_name', key: 'first_name',
}, },
{ {
name: i18n._(t`Last name`), name: i18n._(t`Last Name`),
key: 'last_name', key: 'last_name',
}, },
]} ]}

View File

@@ -20,24 +20,36 @@ function UserTeamList({ i18n }) {
const { id: userId } = useParams(); const { id: userId } = useParams();
const { const {
result: { teams, count }, result: { teams, count, actions, relatedSearchFields },
error: contentError, error: contentError,
isLoading, isLoading,
request: fetchOrgs, request: fetchOrgs,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const { const [
data: { results, count: teamCount }, {
} = await UsersAPI.readTeams(userId, params); data: { results, count: teamCount },
},
actionsResponse,
] = await Promise.all([
UsersAPI.readTeams(userId, params),
UsersAPI.readTeamsOptions(userId),
]);
return { return {
teams: results, teams: results,
count: teamCount, count: teamCount,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
}; };
}, [userId, location.search]), }, [userId, location.search]),
{ {
teams: [], teams: [],
count: 0, count: 0,
actions: {},
relatedSearchFields: [],
} }
); );
@@ -45,6 +57,11 @@ function UserTeamList({ i18n }) {
fetchOrgs(); fetchOrgs();
}, [fetchOrgs]); }, [fetchOrgs]);
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<PaginatedDataList <PaginatedDataList
items={teams} items={teams}
@@ -66,14 +83,16 @@ function UserTeamList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'name', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Organization`), name: i18n._(t`Organization`),
key: 'organization__name', key: 'organization__name__icontains',
}, },
]} ]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/> />
); );
} }

View File

@@ -58,13 +58,14 @@ describe('<UserTeamList />', () => {
data: mockAPIUserTeamList.data, data: mockAPIUserTeamList.data,
}) })
); );
UsersAPI.readOptions = jest.fn(() => UsersAPI.readTeamsOptions = jest.fn(() =>
Promise.resolve({ Promise.resolve({
data: { data: {
actions: { actions: {
GET: {}, GET: {},
POST: {}, POST: {},
}, },
related_search_fields: [],
}, },
}) })
); );

View File

@@ -28,13 +28,19 @@ function UserTokenList({ i18n }) {
error, error,
isLoading, isLoading,
request: fetchTokens, request: fetchTokens,
result: { tokens, itemCount }, result: { tokens, itemCount, actions, relatedSearchFields },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const { const [
data: { results, count }, {
} = await UsersAPI.readTokens(id, params); data: { results, count },
},
actionsResponse,
] = await Promise.all([
UsersAPI.readTokens(id, params),
UsersAPI.readTokenOptions(id),
]);
const modifiedResults = results.map(result => { const modifiedResults = results.map(result => {
result.summary_fields = { result.summary_fields = {
user: result.summary_fields.user, user: result.summary_fields.user,
@@ -44,9 +50,16 @@ function UserTokenList({ i18n }) {
result.name = result.summary_fields.application?.name; result.name = result.summary_fields.application?.name;
return result; return result;
}); });
return { tokens: modifiedResults, itemCount: count }; return {
tokens: modifiedResults,
itemCount: count,
actions: actionsResponse.data.actions,
relatedSearchFields: (
actionsResponse?.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
};
}, [id, location.search]), }, [id, location.search]),
{ tokens: [], itemCount: 0 } { tokens: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
); );
useEffect(() => { useEffect(() => {
@@ -80,6 +93,10 @@ function UserTokenList({ i18n }) {
}; };
const canAdd = true; const canAdd = true;
const relatedSearchableKeys = relatedSearchFields || [];
const searchableKeys = Object.keys(actions?.GET || {}).filter(
key => actions.GET[key].filterable
);
return ( return (
<> <>
<PaginatedDataList <PaginatedDataList
@@ -93,12 +110,12 @@ function UserTokenList({ i18n }) {
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: i18n._(t`Name`), name: i18n._(t`Name`),
key: 'application__name', key: 'application__name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: i18n._(t`Description`), name: i18n._(t`Description`),
key: 'description', key: 'description__icontains',
}, },
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[

View File

@@ -123,8 +123,15 @@ const tokens = {
describe('<UserTokenList />', () => { describe('<UserTokenList />', () => {
let wrapper; let wrapper;
test('should mount properly, and fetch tokens', async () => {
beforeEach(() => {
UsersAPI.readTokens.mockResolvedValue(tokens); UsersAPI.readTokens.mockResolvedValue(tokens);
UsersAPI.readTokenOptions.mockResolvedValue({
data: { related_search_fields: [] },
});
});
test('should mount properly, and fetch tokens', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<UserTokenList />); wrapper = mountWithContexts(<UserTokenList />);
}); });
@@ -137,7 +144,6 @@ describe('<UserTokenList />', () => {
}); });
test('edit button should be disabled', async () => { test('edit button should be disabled', async () => {
UsersAPI.readTokens.mockResolvedValue(tokens);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<UserTokenList />); wrapper = mountWithContexts(<UserTokenList />);
}); });