mirror of
https://github.com/ansible/awx.git
synced 2024-10-30 22:21:13 +03:00
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:
parent
b46a87209a
commit
5ffc2e4188
@ -11,6 +11,10 @@ class Applications extends Base {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
readTokenOptions(appId) {
|
||||
return this.http.options(`${this.baseUrl}${appId}/tokens/`);
|
||||
}
|
||||
}
|
||||
|
||||
export default Applications;
|
||||
|
@ -60,6 +60,10 @@ class Users extends Base {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
readTokenOptions(userId) {
|
||||
return this.http.options(`${this.baseUrl}${userId}/tokens/`);
|
||||
}
|
||||
}
|
||||
|
||||
export default Users;
|
||||
|
@ -156,31 +156,31 @@ class AddResourceRole extends React.Component {
|
||||
const userSearchColumns = [
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
key: 'username__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
key: 'first_name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
key: 'last_name__icontains',
|
||||
},
|
||||
];
|
||||
|
||||
const userSortColumns = [
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
key: 'username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
key: 'first_name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
key: 'last_name__icontains',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -13,7 +13,7 @@ describe('<SelectResourceStep />', () => {
|
||||
const searchColumns = [
|
||||
{
|
||||
name: 'Username',
|
||||
key: 'username',
|
||||
key: 'username__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
@ -114,16 +114,16 @@ function AssociateModal({
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
|
@ -38,7 +38,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
const location = useLocation();
|
||||
const {
|
||||
result: { results, count },
|
||||
result: { results, count, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchJobs,
|
||||
@ -46,12 +46,27 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
useCallback(
|
||||
async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
const { data } = await UnifiedJobsAPI.read({ ...params });
|
||||
return data;
|
||||
const [response, actionsResponse] = await Promise.all([
|
||||
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
|
||||
),
|
||||
{ results: [], count: 0 }
|
||||
{
|
||||
results: [],
|
||||
count: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<>
|
||||
<Card>
|
||||
@ -137,7 +157,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
@ -146,11 +166,11 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Label Name`),
|
||||
key: 'labels__name',
|
||||
key: 'labels__name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Job Type`),
|
||||
key: `type`,
|
||||
key: `or__type`,
|
||||
options: [
|
||||
[`project_update`, i18n._(t`Source Control Update`)],
|
||||
[`inventory_update`, i18n._(t`Inventory Sync`)],
|
||||
@ -162,7 +182,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Launched By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Status`),
|
||||
@ -209,6 +229,8 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
||||
key: 'started',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
|
@ -96,6 +96,16 @@ UnifiedJobsAPI.read.mockResolvedValue({
|
||||
data: { count: 3, results: mockResults },
|
||||
});
|
||||
|
||||
UnifiedJobsAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
function waitForLoaded(wrapper) {
|
||||
return waitForElement(
|
||||
wrapper,
|
||||
|
@ -52,7 +52,7 @@ function CredentialsStep({ i18n }) {
|
||||
}, [fetchTypes]);
|
||||
|
||||
const {
|
||||
result: { credentials, count },
|
||||
result: { credentials, count, actions, relatedSearchFields },
|
||||
error: credentialsError,
|
||||
isLoading: isCredentialsLoading,
|
||||
request: fetchCredentials,
|
||||
@ -62,16 +62,23 @@ function CredentialsStep({ i18n }) {
|
||||
return { credentials: [], count: 0 };
|
||||
}
|
||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||
const { data } = await CredentialsAPI.read({
|
||||
...params,
|
||||
credential_type: selectedType.id,
|
||||
});
|
||||
const [{ data }, actionsResponse] = await Promise.all([
|
||||
CredentialsAPI.read({
|
||||
...params,
|
||||
credential_type: selectedType.id,
|
||||
}),
|
||||
CredentialsAPI.readOptions(),
|
||||
]);
|
||||
return {
|
||||
credentials: data.results,
|
||||
count: data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [selectedType, history.location.search]),
|
||||
{ credentials: [], count: 0 }
|
||||
{ credentials: [], count: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -97,6 +104,11 @@ function CredentialsStep({ i18n }) {
|
||||
/>
|
||||
);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{types && types.length > 0 && (
|
||||
@ -129,16 +141,16 @@ function CredentialsStep({ i18n }) {
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -147,6 +159,8 @@ function CredentialsStep({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
searchableKeys={searchableKeys}
|
||||
relatedSearchableKeys={relatedSearchableKeys}
|
||||
multiple={isVault}
|
||||
header={i18n._(t`Credentials`)}
|
||||
name="credentials"
|
||||
|
@ -31,6 +31,15 @@ describe('CredentialsStep', () => {
|
||||
count: 5,
|
||||
},
|
||||
});
|
||||
CredentialsAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should load credentials', async () => {
|
||||
|
@ -27,20 +27,29 @@ function InventoryStep({ i18n }) {
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
result: { inventories, count },
|
||||
result: { inventories, count, actions, relatedSearchFields },
|
||||
request: fetchInventories,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
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 {
|
||||
inventories: data.results,
|
||||
count: data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [history.location]),
|
||||
{
|
||||
count: 0,
|
||||
inventories: [],
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -48,6 +57,11 @@ function InventoryStep({ i18n }) {
|
||||
fetchInventories();
|
||||
}, [fetchInventories]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@ -63,16 +77,16 @@ function InventoryStep({ i18n }) {
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -81,6 +95,8 @@ function InventoryStep({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
header={i18n._(t`Inventory`)}
|
||||
name="inventory"
|
||||
qsConfig={QS_CONFIG}
|
||||
|
@ -21,6 +21,16 @@ describe('InventoryStep', () => {
|
||||
count: 3,
|
||||
},
|
||||
});
|
||||
|
||||
InventoriesAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should load inventories', async () => {
|
||||
|
@ -22,22 +22,41 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
|
||||
const location = useLocation();
|
||||
const {
|
||||
error,
|
||||
result: { applications, itemCount },
|
||||
result: { applications, itemCount, actions, relatedSearchFields },
|
||||
request: fetchApplications,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
|
||||
const {
|
||||
data: { results, count },
|
||||
} = await ApplicationsAPI.read(params);
|
||||
return { applications: results, itemCount: count };
|
||||
const [
|
||||
{
|
||||
data: { results, 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]),
|
||||
{ applications: [], itemCount: 0 }
|
||||
{ applications: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
useEffect(() => {
|
||||
fetchApplications();
|
||||
}, [fetchApplications]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup fieldId="application" label={label}>
|
||||
<Lookup
|
||||
@ -56,12 +75,12 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
key: 'description__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -82,6 +101,8 @@ function ApplicationLookup({ i18n, onChange, value, label }) {
|
||||
key: 'description',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
readOnly={!canDelete}
|
||||
name="application"
|
||||
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
|
||||
|
@ -35,7 +35,7 @@ function CredentialLookup({
|
||||
tooltip,
|
||||
}) {
|
||||
const {
|
||||
result: { count, credentials },
|
||||
result: { count, credentials, actions, relatedSearchFields },
|
||||
error,
|
||||
request: fetchCredentials,
|
||||
} = useRequest(
|
||||
@ -51,16 +51,23 @@ function CredentialLookup({
|
||||
? { credential_type__namespace: credentialTypeNamespace }
|
||||
: {};
|
||||
|
||||
const { data } = await CredentialsAPI.read(
|
||||
mergeParams(params, {
|
||||
...typeIdParams,
|
||||
...typeKindParams,
|
||||
...typeNamespaceParams,
|
||||
})
|
||||
);
|
||||
const [{ data }, actionsResponse] = await Promise.all([
|
||||
CredentialsAPI.read(
|
||||
mergeParams(params, {
|
||||
...typeIdParams,
|
||||
...typeKindParams,
|
||||
...typeNamespaceParams,
|
||||
})
|
||||
),
|
||||
CredentialsAPI.readOptions,
|
||||
]);
|
||||
return {
|
||||
count: data.count,
|
||||
credentials: data.results,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [
|
||||
credentialTypeId,
|
||||
@ -71,6 +78,8 @@ function CredentialLookup({
|
||||
{
|
||||
count: 0,
|
||||
credentials: [],
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -78,6 +87,11 @@ function CredentialLookup({
|
||||
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
|
||||
|
||||
return (
|
||||
@ -107,16 +121,16 @@ function CredentialLookup({
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -125,6 +139,8 @@ function CredentialLookup({
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
readOnly={!canDelete}
|
||||
name="credential"
|
||||
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
|
||||
|
@ -19,26 +19,38 @@ const QS_CONFIG = getQSConfig('inventory', {
|
||||
|
||||
function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
|
||||
const {
|
||||
result: { inventories, count },
|
||||
result: { inventories, count, actions, relatedSearchFields },
|
||||
request: fetchInventories,
|
||||
error,
|
||||
isLoading,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
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 {
|
||||
inventories: data.results,
|
||||
count: data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [history.location]),
|
||||
{ inventories: [], count: 0 }
|
||||
{ inventories: [], count: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchInventories();
|
||||
}, [fetchInventories]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Lookup
|
||||
@ -58,16 +70,16 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -76,6 +88,8 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
multiple={state.multiple}
|
||||
header={i18n._(t`Inventory`)}
|
||||
name="inventory"
|
||||
|
156
awx/ui_next/src/components/Lookup/InventoryScriptLookup.jsx
Normal file
156
awx/ui_next/src/components/Lookup/InventoryScriptLookup.jsx
Normal 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));
|
@ -49,7 +49,7 @@ function MultiCredentialsLookup(props) {
|
||||
}, [fetchTypes]);
|
||||
|
||||
const {
|
||||
result: { credentials, credentialsCount },
|
||||
result: { credentials, credentialsCount, actions, relatedSearchFields },
|
||||
request: fetchCredentials,
|
||||
error: credentialsError,
|
||||
isLoading: isCredentialsLoading,
|
||||
@ -62,15 +62,24 @@ function MultiCredentialsLookup(props) {
|
||||
};
|
||||
}
|
||||
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 {
|
||||
credentials: results,
|
||||
credentialsCount: count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [selectedType, history.location]),
|
||||
{
|
||||
credentials: [],
|
||||
credentialsCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -95,6 +104,11 @@ function MultiCredentialsLookup(props) {
|
||||
|
||||
const isVault = selectedType?.kind === 'vault';
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<Lookup
|
||||
id="multiCredential"
|
||||
@ -149,16 +163,16 @@ function MultiCredentialsLookup(props) {
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -167,6 +181,8 @@ function MultiCredentialsLookup(props) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
multiple={isVault}
|
||||
header={i18n._(t`Credentials`)}
|
||||
name="credentials"
|
||||
|
@ -43,6 +43,15 @@ describe('<MultiCredentialsLookup />', () => {
|
||||
count: 3,
|
||||
},
|
||||
});
|
||||
CredentialsAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -80,16 +80,16 @@ function OrganizationLookup({
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
|
@ -32,25 +32,34 @@ function ProjectLookup({
|
||||
history,
|
||||
}) {
|
||||
const {
|
||||
result: { projects, count },
|
||||
result: { projects, count, actions, relatedSearchFields },
|
||||
request: fetchProjects,
|
||||
error,
|
||||
isLoading,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
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) {
|
||||
autocomplete(data.results[0]);
|
||||
}
|
||||
return {
|
||||
count: data.count,
|
||||
projects: data.results,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [history.location.search, autocomplete]),
|
||||
{
|
||||
count: 0,
|
||||
projects: [],
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -58,6 +67,11 @@ function ProjectLookup({
|
||||
fetchProjects();
|
||||
}, [fetchProjects]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="project"
|
||||
@ -83,12 +97,12 @@ function ProjectLookup({
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'scm_type',
|
||||
key: 'or__scm_type',
|
||||
options: [
|
||||
[``, i18n._(t`Manual`)],
|
||||
[`git`, i18n._(t`Git`)],
|
||||
@ -99,15 +113,15 @@ function ProjectLookup({
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source Control URL`),
|
||||
key: 'scm_url',
|
||||
key: 'scm_url__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
@ -116,6 +130,8 @@ function ProjectLookup({
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
options={projects}
|
||||
optionCount={count}
|
||||
multiple={state.multiple}
|
||||
|
@ -145,12 +145,12 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'type',
|
||||
key: 'or__type',
|
||||
options: [
|
||||
['email', i18n._(t`Email`)],
|
||||
['grafana', i18n._(t`Grafana`)],
|
||||
@ -165,12 +165,12 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -30,6 +30,8 @@ function OptionsList({
|
||||
optionCount,
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
searchableKeys,
|
||||
relatedSearchableKeys,
|
||||
multiple,
|
||||
header,
|
||||
name,
|
||||
@ -61,6 +63,8 @@ function OptionsList({
|
||||
qsConfig={qsConfig}
|
||||
toolbarSearchColumns={searchColumns}
|
||||
toolbarSortColumns={sortColumns}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
hasContentLoading={isLoading}
|
||||
onRowClick={selectItem}
|
||||
renderItem={item => (
|
||||
|
@ -17,7 +17,9 @@ describe('<OptionsList />', () => {
|
||||
value={[]}
|
||||
options={options}
|
||||
optionCount={3}
|
||||
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]}
|
||||
searchColumns={[
|
||||
{ name: 'Foo', key: 'foo__icontains', isDefault: true },
|
||||
]}
|
||||
sortColumns={[{ name: 'Foo', key: 'foo' }]}
|
||||
qsConfig={qsConfig}
|
||||
selectItem={() => {}}
|
||||
@ -40,7 +42,9 @@ describe('<OptionsList />', () => {
|
||||
value={[options[1]]}
|
||||
options={options}
|
||||
optionCount={3}
|
||||
searchColumns={[{ name: 'Foo', key: 'foo', isDefault: true }]}
|
||||
searchColumns={[
|
||||
{ name: 'Foo', key: 'foo__icontains', isDefault: true },
|
||||
]}
|
||||
sortColumns={[{ name: 'Foo', key: 'foo' }]}
|
||||
qsConfig={qsConfig}
|
||||
selectItem={() => {}}
|
||||
|
@ -80,16 +80,16 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
key: 'username__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First name`),
|
||||
key: 'first_name',
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last name`),
|
||||
key: 'last_name',
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -98,11 +98,11 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
|
||||
key: 'username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First name`),
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last name`),
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
},
|
||||
]}
|
||||
|
@ -32,7 +32,7 @@ function ScheduleList({
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
result: { schedules, itemCount, actions },
|
||||
result: { schedules, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchSchedules,
|
||||
@ -49,12 +49,16 @@ function ScheduleList({
|
||||
schedules: results,
|
||||
itemCount: count,
|
||||
actions: scheduleActions.data.actions,
|
||||
relatedSearchFields: (
|
||||
scheduleActions?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location, loadSchedules, loadScheduleOptions]),
|
||||
{
|
||||
schedules: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -102,6 +106,10 @@ function ScheduleList({
|
||||
actions &&
|
||||
Object.prototype.hasOwnProperty.call(actions, 'POST') &&
|
||||
!hideAddButton;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -123,7 +131,7 @@ function ScheduleList({
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
]}
|
||||
@ -141,6 +149,8 @@ function ScheduleList({
|
||||
key: 'unified_job_template__polymorphic_ctype__model',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -82,7 +82,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
fetchItems: JobTemplatesAPI.read,
|
||||
label: 'Job template',
|
||||
selectedResource: 'jobTemplate',
|
||||
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }],
|
||||
searchColumns: [
|
||||
{ name: 'Name', key: 'name__icontains', isDefault: true },
|
||||
],
|
||||
sortColumns: [{ name: 'Name', key: 'name' }],
|
||||
})
|
||||
);
|
||||
@ -116,7 +118,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
fetchItems: JobTemplatesAPI.read,
|
||||
label: 'Job template',
|
||||
selectedResource: 'jobTemplate',
|
||||
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }],
|
||||
searchColumns: [
|
||||
{ name: 'Name', key: 'name__icontains', isDefault: true },
|
||||
],
|
||||
sortColumns: [{ name: 'Name', key: 'name' }],
|
||||
})
|
||||
);
|
||||
@ -190,7 +194,9 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
fetchItems: JobTemplatesAPI.read,
|
||||
label: 'Job template',
|
||||
selectedResource: 'jobTemplate',
|
||||
searchColumns: [{ name: 'Name', key: 'name', isDefault: true }],
|
||||
searchColumns: [
|
||||
{ name: 'Name', key: 'name__icontains', isDefault: true },
|
||||
],
|
||||
sortColumns: [{ name: 'Name', key: 'name' }],
|
||||
})
|
||||
);
|
||||
|
@ -16,20 +16,20 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Playbook name`),
|
||||
key: 'playbook',
|
||||
key: 'playbook__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
@ -46,20 +46,20 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Playbook name`),
|
||||
key: 'playbook',
|
||||
key: 'playbook__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
@ -76,12 +76,12 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'scm_type',
|
||||
key: 'or__scm_type',
|
||||
options: [
|
||||
[``, i18n._(t`Manual`)],
|
||||
[`git`, i18n._(t`Git`)],
|
||||
@ -92,15 +92,15 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source Control URL`),
|
||||
key: 'scm_url',
|
||||
key: 'scm_url__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
@ -117,16 +117,16 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
@ -143,12 +143,12 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'scm_type',
|
||||
key: 'or__scm_type',
|
||||
options: [
|
||||
[``, i18n._(t`Manual`)],
|
||||
[`git`, i18n._(t`Git`)],
|
||||
@ -159,15 +159,15 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source Control URL`),
|
||||
key: 'scm_url',
|
||||
key: 'scm_url__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
@ -184,16 +184,16 @@ export default function getResourceAccessConfig(i18n) {
|
||||
searchColumns: [
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
|
@ -26,14 +26,20 @@ function ApplicationTokenList({ i18n }) {
|
||||
const {
|
||||
error,
|
||||
isLoading,
|
||||
result: { tokens, itemCount },
|
||||
result: { tokens, itemCount, actions, relatedSearchFields },
|
||||
request: fetchTokens,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
const {
|
||||
data: { results, count },
|
||||
} = await ApplicationsAPI.readTokens(id, params);
|
||||
const [
|
||||
{
|
||||
data: { results, count },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([
|
||||
ApplicationsAPI.readTokens(id, params),
|
||||
ApplicationsAPI.readTokenOptions(id),
|
||||
]);
|
||||
const modifiedResults = results.map(result => {
|
||||
result.summary_fields = {
|
||||
user: result.summary_fields.user,
|
||||
@ -43,9 +49,16 @@ function ApplicationTokenList({ i18n }) {
|
||||
result.name = result.summary_fields.user?.username;
|
||||
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]),
|
||||
{ tokens: [], itemCount: 0 }
|
||||
{ tokens: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -77,6 +90,12 @@ function ApplicationTokenList({ i18n }) {
|
||||
await handleDeleteApplications();
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaginatedDataList
|
||||
@ -90,7 +109,7 @@ function ApplicationTokenList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'user__username',
|
||||
key: 'user__username__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
]}
|
||||
@ -116,6 +135,8 @@ function ApplicationTokenList({ i18n }) {
|
||||
key: 'modified',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
|
@ -82,6 +82,19 @@ const tokens = {
|
||||
};
|
||||
describe('<ApplicationTokenList/>', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
ApplicationsAPI.readTokenOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should mount properly', async () => {
|
||||
ApplicationsAPI.readTokens.mockResolvedValue(tokens);
|
||||
await act(async () => {
|
||||
|
@ -32,7 +32,7 @@ function ApplicationsList({ i18n }) {
|
||||
isLoading,
|
||||
error,
|
||||
request: fetchApplications,
|
||||
result: { applications, itemCount, actions },
|
||||
result: { applications, itemCount, actions, relatedSearchFields },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
@ -46,12 +46,16 @@ function ApplicationsList({ i18n }) {
|
||||
applications: response.data.results,
|
||||
itemCount: response.data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
applications: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -85,6 +89,10 @@ function ApplicationsList({ i18n }) {
|
||||
};
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -101,12 +109,12 @@ function ApplicationsList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
key: 'description__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -127,6 +135,8 @@ function ApplicationsList({ i18n }) {
|
||||
key: 'description',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
|
@ -20,6 +20,16 @@ CredentialsAPI.read.mockResolvedValue({
|
||||
},
|
||||
});
|
||||
|
||||
CredentialsAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
CredentialTypesAPI.readDetail.mockResolvedValue({
|
||||
data: {
|
||||
id: 20,
|
||||
|
@ -25,28 +25,38 @@ function CredentialsStep({ i18n }) {
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
result: { credentials, count },
|
||||
result: { credentials, count, actions, relatedSearchFields },
|
||||
error: credentialsError,
|
||||
isLoading: isCredentialsLoading,
|
||||
request: fetchCredentials,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||
const { data } = await CredentialsAPI.read({
|
||||
...params,
|
||||
});
|
||||
const [{ data }, actionsResponse] = await Promise.all([
|
||||
CredentialsAPI.read({ ...params }),
|
||||
CredentialsAPI.readOptions(),
|
||||
]);
|
||||
return {
|
||||
credentials: data.results,
|
||||
count: data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [history.location.search]),
|
||||
{ credentials: [], count: 0 }
|
||||
{ credentials: [], count: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCredentials();
|
||||
}, [fetchCredentials]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
if (credentialsError) {
|
||||
return <ContentError error={credentialsError} />;
|
||||
}
|
||||
@ -76,16 +86,16 @@ function CredentialsStep({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -94,6 +104,8 @@ function CredentialsStep({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ function HostGroupsList({ i18n, host }) {
|
||||
const invId = host.summary_fields.inventory.id;
|
||||
|
||||
const {
|
||||
result: { groups, itemCount, actions },
|
||||
result: { groups, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchGroups,
|
||||
@ -55,11 +55,16 @@ function HostGroupsList({ i18n, host }) {
|
||||
groups: results,
|
||||
itemCount: count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [hostId, search]),
|
||||
{
|
||||
groups: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -123,6 +128,10 @@ function HostGroupsList({ i18n, host }) {
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -136,16 +145,16 @@ function HostGroupsList({ i18n, host }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -154,6 +163,8 @@ function HostGroupsList({ i18n, host }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderItem={item => (
|
||||
<HostGroupItem
|
||||
key={item.id}
|
||||
|
@ -29,7 +29,7 @@ function HostList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: { hosts, count, actions },
|
||||
result: { hosts, count, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchHosts,
|
||||
@ -44,12 +44,16 @@ function HostList({ i18n }) {
|
||||
hosts: results[0].data.results,
|
||||
count: results[0].data.count,
|
||||
actions: results[1].data.actions,
|
||||
relatedSearchFields: (
|
||||
results[1]?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
hosts: [],
|
||||
count: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -93,6 +97,10 @@ function HostList({ i18n }) {
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
@ -108,16 +116,16 @@ function HostList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -126,6 +134,8 @@ function HostList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -32,7 +32,7 @@ function InventoryGroupHostList({ i18n }) {
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
result: { hosts, hostCount, actions },
|
||||
result: { hosts, hostCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchHosts,
|
||||
@ -48,11 +48,16 @@ function InventoryGroupHostList({ i18n }) {
|
||||
hosts: response.data.results,
|
||||
hostCount: response.data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [groupId, inventoryId, location.search]),
|
||||
{
|
||||
hosts: [],
|
||||
hostCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -122,6 +127,10 @@ function InventoryGroupHostList({ i18n }) {
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
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 (
|
||||
<>
|
||||
@ -136,16 +145,16 @@ function InventoryGroupHostList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -154,6 +163,8 @@ function InventoryGroupHostList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -141,7 +141,7 @@ function InventoryGroupsList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
@ -154,12 +154,12 @@ function InventoryGroupsList({ i18n }) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -31,7 +31,7 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
const { search } = useLocation();
|
||||
|
||||
const {
|
||||
result: { groups, itemCount, actions },
|
||||
result: { groups, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchGroups,
|
||||
@ -53,11 +53,16 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
groups: results,
|
||||
itemCount: count,
|
||||
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
|
||||
{
|
||||
groups: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -121,6 +126,10 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -134,16 +143,16 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -152,6 +161,8 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderItem={item => (
|
||||
<InventoryHostGroupItem
|
||||
key={item.id}
|
||||
|
@ -29,7 +29,7 @@ function InventoryList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: { results, itemCount, actions },
|
||||
result: { results, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchInventories,
|
||||
@ -44,12 +44,16 @@ function InventoryList({ i18n }) {
|
||||
results: response.data.results,
|
||||
itemCount: response.data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
results: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -93,6 +97,10 @@ function InventoryList({ i18n }) {
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...inventories] : []);
|
||||
@ -135,16 +143,16 @@ function InventoryList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -153,6 +161,8 @@ function InventoryList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
|
@ -31,7 +31,7 @@ function OrganizationsList({ i18n }) {
|
||||
const addUrl = `${match.url}/add`;
|
||||
|
||||
const {
|
||||
result: { organizations, organizationCount, actions },
|
||||
result: { organizations, organizationCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading: isOrgsLoading,
|
||||
request: fetchOrganizations,
|
||||
@ -46,12 +46,16 @@ function OrganizationsList({ i18n }) {
|
||||
organizations: orgs.data.results,
|
||||
organizationCount: orgs.data.count,
|
||||
actions: orgActions.data.actions,
|
||||
relatedSearchFields: (
|
||||
orgActions?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
organizations: [],
|
||||
organizationCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -86,6 +90,10 @@ function OrganizationsList({ i18n }) {
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isOrgsLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...organizations] : []);
|
||||
@ -114,16 +122,16 @@ function OrganizationsList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -132,6 +140,8 @@ function OrganizationsList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -53,16 +53,16 @@ function OrganizationTeamList({ id, i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -103,21 +103,12 @@ function ProjectJobTemplatesList({ i18n }) {
|
||||
onRowClick={handleSelect}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true,
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Playbook name`),
|
||||
key: 'job_template__playbook',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -30,7 +30,7 @@ function ProjectList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: { results, itemCount, actions },
|
||||
result: { results, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchProjects,
|
||||
@ -45,12 +45,16 @@ function ProjectList({ i18n }) {
|
||||
results: response.data.results,
|
||||
itemCount: response.data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
results: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -85,6 +89,10 @@ function ProjectList({ i18n }) {
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...projects] : []);
|
||||
@ -113,12 +121,12 @@ function ProjectList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'scm_type',
|
||||
key: 'or__scm_type',
|
||||
options: [
|
||||
[``, i18n._(t`Manual`)],
|
||||
[`git`, i18n._(t`Git`)],
|
||||
@ -129,17 +137,19 @@ function ProjectList({ i18n }) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source Control URL`),
|
||||
key: 'scm_url',
|
||||
key: 'scm_url__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
|
@ -78,6 +78,7 @@ describe('<ProjectList />', () => {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -29,7 +29,7 @@ function TeamList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: { teams, itemCount, actions },
|
||||
result: { teams, itemCount, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchTeams,
|
||||
@ -44,12 +44,16 @@ function TeamList({ i18n }) {
|
||||
teams: response.data.results,
|
||||
itemCount: response.data.count,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [location]),
|
||||
{
|
||||
teams: [],
|
||||
itemCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -81,6 +85,10 @@ function TeamList({ i18n }) {
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...teams] : []);
|
||||
@ -109,20 +117,20 @@ function TeamList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Organization Name`),
|
||||
key: 'organization__name',
|
||||
key: 'organization__name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -131,6 +139,8 @@ function TeamList({ i18n }) {
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -37,7 +37,7 @@ function TeamRolesList({ i18n, me, team }) {
|
||||
isLoading,
|
||||
request: fetchRoles,
|
||||
contentError,
|
||||
result: { roleCount, roles, isAdminOfOrg },
|
||||
result: { roleCount, roles, isAdminOfOrg, actions, relatedSearchFields },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, search);
|
||||
@ -46,22 +46,30 @@ function TeamRolesList({ i18n, me, team }) {
|
||||
data: { results, count },
|
||||
},
|
||||
{ count: orgAdminCount },
|
||||
actionsResponse,
|
||||
] = await Promise.all([
|
||||
TeamsAPI.readRoles(team.id, params),
|
||||
UsersAPI.readAdminOfOrganizations(me.id, {
|
||||
id: team.organization,
|
||||
}),
|
||||
TeamsAPI.readRoleOptions(team.id),
|
||||
]);
|
||||
return {
|
||||
roleCount: count,
|
||||
roles: results,
|
||||
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]),
|
||||
{
|
||||
roles: [],
|
||||
roleCount: 0,
|
||||
isAdminOfOrg: false,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -90,6 +98,10 @@ function TeamRolesList({ i18n, me, team }) {
|
||||
);
|
||||
|
||||
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 { resource_id, resource_type } = role.summary_fields;
|
||||
@ -136,16 +148,18 @@ function TeamRolesList({ i18n, me, team }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Role`),
|
||||
key: 'role_field',
|
||||
key: 'role_field__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
name: i18n._(t`ID`),
|
||||
key: 'id',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
|
@ -164,6 +164,13 @@ describe('<TeamRolesList />', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
TeamsAPI.readRoleOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: { GET: {} },
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -36,7 +36,7 @@ function TemplateList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: { results, count, jtActions, wfjtActions },
|
||||
result: { results, count, jtActions, wfjtActions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchTemplates,
|
||||
@ -53,6 +53,14 @@ function TemplateList({ i18n }) {
|
||||
count: responses[0].data.count,
|
||||
jtActions: responses[1].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]),
|
||||
{
|
||||
@ -60,6 +68,7 @@ function TemplateList({ i18n }) {
|
||||
count: 0,
|
||||
jtActions: {},
|
||||
wfjtActions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -118,6 +127,18 @@ function TemplateList({ i18n }) {
|
||||
jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
|
||||
const canAddWFJT =
|
||||
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 = [];
|
||||
|
||||
if (canAddJT) {
|
||||
@ -152,16 +173,16 @@ function TemplateList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
key: 'description__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'type',
|
||||
key: 'or__type',
|
||||
options: [
|
||||
[`job_template`, i18n._(t`Job Template`)],
|
||||
[`workflow_job_template`, i18n._(t`Workflow Template`)],
|
||||
@ -169,15 +190,15 @@ function TemplateList({ i18n }) {
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Playbook name`),
|
||||
key: 'job_template__playbook',
|
||||
key: 'job_template__playbook__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -206,6 +227,8 @@ function TemplateList({ i18n }) {
|
||||
key: 'type',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
|
@ -68,12 +68,12 @@ function InventorySourcesList({ i18n, nodeResource, onUpdateNodeResource }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source`),
|
||||
key: 'source',
|
||||
key: 'or__source',
|
||||
options: [
|
||||
[`file`, i18n._(t`File, directory or script`)],
|
||||
[`scm`, i18n._(t`Sourced from a project`)],
|
||||
|
@ -70,20 +70,20 @@ function JobTemplatesList({ i18n, nodeResource, onUpdateNodeResource }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Playbook name`),
|
||||
key: 'playbook',
|
||||
key: 'playbook__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -68,12 +68,12 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Type`),
|
||||
key: 'scm_type',
|
||||
key: 'or__scm_type',
|
||||
options: [
|
||||
[``, i18n._(t`Manual`)],
|
||||
[`git`, i18n._(t`Git`)],
|
||||
@ -83,16 +83,16 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Source control URL`),
|
||||
key: 'scm_url',
|
||||
name: i18n._(t`Source Control URL`),
|
||||
key: 'scm_url__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -74,24 +74,24 @@ function WorkflowJobTemplatesList({
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Organization (name)`),
|
||||
key: 'organization__name',
|
||||
name: i18n._(t`Organization (Name)`),
|
||||
key: 'organization__name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Inventory (name)`),
|
||||
key: 'inventory__name',
|
||||
name: i18n._(t`Inventory (Name)`),
|
||||
key: 'inventory__name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created by (username)`),
|
||||
key: 'created_by__username',
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified by (username)`),
|
||||
key: 'modified_by__username',
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -32,13 +32,13 @@ const QS_CONFIG = getQSConfig('roles', {
|
||||
function UserAccessList({ i18n, user }) {
|
||||
const { search } = useLocation();
|
||||
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
||||
|
||||
const [roleToDisassociate, setRoleToDisassociate] = useState(null);
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
request: fetchRoles,
|
||||
error,
|
||||
result: { roleCount, roles, options },
|
||||
result: { roleCount, roles, actions, relatedSearchFields },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, search);
|
||||
@ -46,20 +46,28 @@ function UserAccessList({ i18n, user }) {
|
||||
{
|
||||
data: { results, count },
|
||||
},
|
||||
{
|
||||
data: { actions },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([
|
||||
UsersAPI.readRoles(user.id, params),
|
||||
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]),
|
||||
{
|
||||
roles: [],
|
||||
roleCount: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRoles();
|
||||
}, [fetchRoles]);
|
||||
@ -82,7 +90,12 @@ function UserAccessList({ i18n, user }) {
|
||||
|
||||
const canAdd =
|
||||
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 = () => {
|
||||
setIsWizardOpen(false);
|
||||
@ -132,16 +145,18 @@ function UserAccessList({ i18n, user }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Role`),
|
||||
key: 'role_field',
|
||||
key: 'role_field__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
name: i18n._(t`ID`),
|
||||
key: 'id',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderItem={role => {
|
||||
return (
|
||||
<UserAccessListItem
|
||||
|
@ -12,9 +12,8 @@ jest.mock('../../../api/models/Roles');
|
||||
|
||||
UsersAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
},
|
||||
actions: { 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 () => {
|
||||
UsersAPI.readRoleOptions.mockResolvedValueOnce({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
UsersAPI.readRoles.mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
|
@ -98,16 +98,16 @@ function UserList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Username`),
|
||||
key: 'username',
|
||||
key: 'username__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First name`),
|
||||
key: 'first_name',
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name__icontains',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last name`),
|
||||
key: 'last_name',
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
@ -116,11 +116,11 @@ function UserList({ i18n }) {
|
||||
key: 'username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`First name`),
|
||||
name: i18n._(t`First Name`),
|
||||
key: 'first_name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Last name`),
|
||||
name: i18n._(t`Last Name`),
|
||||
key: 'last_name',
|
||||
},
|
||||
]}
|
||||
|
@ -20,24 +20,36 @@ function UserTeamList({ i18n }) {
|
||||
const { id: userId } = useParams();
|
||||
|
||||
const {
|
||||
result: { teams, count },
|
||||
result: { teams, count, actions, relatedSearchFields },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
request: fetchOrgs,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
const {
|
||||
data: { results, count: teamCount },
|
||||
} = await UsersAPI.readTeams(userId, params);
|
||||
const [
|
||||
{
|
||||
data: { results, count: teamCount },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([
|
||||
UsersAPI.readTeams(userId, params),
|
||||
UsersAPI.readTeamsOptions(userId),
|
||||
]);
|
||||
return {
|
||||
teams: results,
|
||||
count: teamCount,
|
||||
actions: actionsResponse.data.actions,
|
||||
relatedSearchFields: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
};
|
||||
}, [userId, location.search]),
|
||||
{
|
||||
teams: [],
|
||||
count: 0,
|
||||
actions: {},
|
||||
relatedSearchFields: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -45,6 +57,11 @@ function UserTeamList({ i18n }) {
|
||||
fetchOrgs();
|
||||
}, [fetchOrgs]);
|
||||
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
|
||||
return (
|
||||
<PaginatedDataList
|
||||
items={teams}
|
||||
@ -66,14 +83,16 @@ function UserTeamList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Organization`),
|
||||
key: 'organization__name',
|
||||
key: 'organization__name__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -58,13 +58,14 @@ describe('<UserTeamList />', () => {
|
||||
data: mockAPIUserTeamList.data,
|
||||
})
|
||||
);
|
||||
UsersAPI.readOptions = jest.fn(() =>
|
||||
UsersAPI.readTeamsOptions = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -28,13 +28,19 @@ function UserTokenList({ i18n }) {
|
||||
error,
|
||||
isLoading,
|
||||
request: fetchTokens,
|
||||
result: { tokens, itemCount },
|
||||
result: { tokens, itemCount, actions, relatedSearchFields },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
const {
|
||||
data: { results, count },
|
||||
} = await UsersAPI.readTokens(id, params);
|
||||
const [
|
||||
{
|
||||
data: { results, count },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([
|
||||
UsersAPI.readTokens(id, params),
|
||||
UsersAPI.readTokenOptions(id),
|
||||
]);
|
||||
const modifiedResults = results.map(result => {
|
||||
result.summary_fields = {
|
||||
user: result.summary_fields.user,
|
||||
@ -44,9 +50,16 @@ function UserTokenList({ i18n }) {
|
||||
result.name = result.summary_fields.application?.name;
|
||||
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]),
|
||||
{ tokens: [], itemCount: 0 }
|
||||
{ tokens: [], itemCount: 0, actions: {}, relatedSearchFields: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -80,6 +93,10 @@ function UserTokenList({ i18n }) {
|
||||
};
|
||||
|
||||
const canAdd = true;
|
||||
const relatedSearchableKeys = relatedSearchFields || [];
|
||||
const searchableKeys = Object.keys(actions?.GET || {}).filter(
|
||||
key => actions.GET[key].filterable
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<PaginatedDataList
|
||||
@ -93,12 +110,12 @@ function UserTokenList({ i18n }) {
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'application__name',
|
||||
key: 'application__name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
key: 'description__icontains',
|
||||
},
|
||||
]}
|
||||
toolbarSortColumns={[
|
||||
|
@ -123,8 +123,15 @@ const tokens = {
|
||||
|
||||
describe('<UserTokenList />', () => {
|
||||
let wrapper;
|
||||
test('should mount properly, and fetch tokens', async () => {
|
||||
|
||||
beforeEach(() => {
|
||||
UsersAPI.readTokens.mockResolvedValue(tokens);
|
||||
UsersAPI.readTokenOptions.mockResolvedValue({
|
||||
data: { related_search_fields: [] },
|
||||
});
|
||||
});
|
||||
|
||||
test('should mount properly, and fetch tokens', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenList />);
|
||||
});
|
||||
@ -137,7 +144,6 @@ describe('<UserTokenList />', () => {
|
||||
});
|
||||
|
||||
test('edit button should be disabled', async () => {
|
||||
UsersAPI.readTokens.mockResolvedValue(tokens);
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenList />);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user