mirror of
https://github.com/ansible/awx.git
synced 2026-01-19 21:51:26 -03:30
add searchable keys support for AssociateModal and SelectResourceStep lists
This commit is contained in:
parent
aee2a81b27
commit
d03448aa9d
@ -11,8 +11,12 @@ import { TeamsAPI, UsersAPI } from '../../api';
|
||||
const readUsers = async queryParams =>
|
||||
UsersAPI.read(Object.assign(queryParams, { is_superuser: false }));
|
||||
|
||||
const readUsersOptions = async () => UsersAPI.readOptions();
|
||||
|
||||
const readTeams = async queryParams => TeamsAPI.read(queryParams);
|
||||
|
||||
const readTeamsOptions = async () => TeamsAPI.readOptions();
|
||||
|
||||
class AddResourceRole extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -259,6 +263,7 @@ class AddResourceRole extends React.Component {
|
||||
displayKey="username"
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
fetchItems={readUsers}
|
||||
fetchOptions={readUsersOptions}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
@ -270,6 +275,7 @@ class AddResourceRole extends React.Component {
|
||||
sortColumns={teamSortColumns}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
fetchItems={readTeams}
|
||||
fetchOptions={readTeamsOptions}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
/>
|
||||
|
||||
@ -29,6 +29,7 @@ function SelectResourceStep({
|
||||
selectedLabel,
|
||||
selectedResourceRows,
|
||||
fetchItems,
|
||||
fetchOptions,
|
||||
i18n,
|
||||
}) {
|
||||
const location = useLocation();
|
||||
@ -37,7 +38,7 @@ function SelectResourceStep({
|
||||
isLoading,
|
||||
error,
|
||||
request: readResourceList,
|
||||
result: { resources, itemCount },
|
||||
result: { resources, itemCount, relatedSearchableKeys, searchableKeys },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const queryParams = parseQueryString(
|
||||
@ -45,14 +46,28 @@ function SelectResourceStep({
|
||||
location.search
|
||||
);
|
||||
|
||||
const {
|
||||
data: { count, results },
|
||||
} = await fetchItems(queryParams);
|
||||
return { resources: results, itemCount: count };
|
||||
}, [location, fetchItems, sortColumns]),
|
||||
const [
|
||||
{
|
||||
data: { count, results },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([fetchItems(queryParams), fetchOptions()]);
|
||||
return {
|
||||
resources: results,
|
||||
itemCount: count,
|
||||
relatedSearchableKeys: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
searchableKeys: Object.keys(
|
||||
actionsResponse.data.actions?.GET || {}
|
||||
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
|
||||
};
|
||||
}, [location, fetchItems, fetchOptions, sortColumns]),
|
||||
{
|
||||
resources: [],
|
||||
itemCount: 0,
|
||||
relatedSearchableKeys: [],
|
||||
searchableKeys: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -84,6 +99,8 @@ function SelectResourceStep({
|
||||
onRowClick={onRowClick}
|
||||
toolbarSearchColumns={searchColumns}
|
||||
toolbarSortColumns={sortColumns}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(i => i.id === item.id)}
|
||||
|
||||
@ -35,6 +35,7 @@ describe('<SelectResourceStep />', () => {
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
fetchItems={() => {}}
|
||||
fetchOptions={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -49,6 +50,15 @@ describe('<SelectResourceStep />', () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
const options = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
@ -58,6 +68,7 @@ describe('<SelectResourceStep />', () => {
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
fetchItems={handleSearch}
|
||||
fetchOptions={options}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -78,6 +89,15 @@ describe('<SelectResourceStep />', () => {
|
||||
{ id: 2, username: 'bar', url: 'item/2' },
|
||||
],
|
||||
};
|
||||
const options = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
@ -87,6 +107,7 @@ describe('<SelectResourceStep />', () => {
|
||||
displayKey="username"
|
||||
onRowClick={handleRowClick}
|
||||
fetchItems={() => ({ data })}
|
||||
fetchOptions={options}
|
||||
selectedResourceRows={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -21,6 +21,7 @@ function AssociateModal({
|
||||
onClose,
|
||||
onAssociate,
|
||||
fetchRequest,
|
||||
optionsRequest,
|
||||
isModalOpen = false,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
@ -28,24 +29,35 @@ function AssociateModal({
|
||||
|
||||
const {
|
||||
request: fetchItems,
|
||||
result: { items, itemCount },
|
||||
result: { items, itemCount, relatedSearchableKeys, searchableKeys },
|
||||
error: contentError,
|
||||
isLoading,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||
const {
|
||||
data: { count, results },
|
||||
} = await fetchRequest(params);
|
||||
const [
|
||||
{
|
||||
data: { count, results },
|
||||
},
|
||||
actionsResponse,
|
||||
] = await Promise.all([fetchRequest(params), optionsRequest()]);
|
||||
|
||||
return {
|
||||
items: results,
|
||||
itemCount: count,
|
||||
relatedSearchableKeys: (
|
||||
actionsResponse?.data?.related_search_fields || []
|
||||
).map(val => val.slice(0, -8)),
|
||||
searchableKeys: Object.keys(
|
||||
actionsResponse.data.actions?.GET || {}
|
||||
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
|
||||
};
|
||||
}, [fetchRequest, history.location.search]),
|
||||
}, [fetchRequest, optionsRequest, history.location.search]),
|
||||
{
|
||||
items: [],
|
||||
itemCount: 0,
|
||||
relatedSearchableKeys: [],
|
||||
searchableKeys: [],
|
||||
}
|
||||
);
|
||||
|
||||
@ -132,6 +144,8 @@ function AssociateModal({
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
searchableKeys={searchableKeys}
|
||||
relatedSearchableKeys={relatedSearchableKeys}
|
||||
/>
|
||||
</Modal>
|
||||
</Fragment>
|
||||
|
||||
@ -15,6 +15,15 @@ describe('<AssociateModal />', () => {
|
||||
const onClose = jest.fn();
|
||||
const onAssociate = jest.fn().mockResolvedValue();
|
||||
const fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } });
|
||||
const optionsRequest = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
@ -23,6 +32,7 @@ describe('<AssociateModal />', () => {
|
||||
onClose={onClose}
|
||||
onAssociate={onAssociate}
|
||||
fetchRequest={fetchRequest}
|
||||
optionsRequest={optionsRequest}
|
||||
isModalOpen
|
||||
/>
|
||||
);
|
||||
|
||||
@ -96,6 +96,7 @@ function UserAndTeamAccessAdd({
|
||||
displayKey="name"
|
||||
onRowClick={handleResourceSelect}
|
||||
fetchItems={selectedResourceType.fetchItems}
|
||||
fetchOptions={selectedResourceType.fetchOptions}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={resourcesSelected}
|
||||
sortedColumnKey="username"
|
||||
|
||||
@ -43,6 +43,15 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
count: 1,
|
||||
},
|
||||
};
|
||||
const options = {
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
};
|
||||
let wrapper;
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
@ -111,11 +120,13 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
|
||||
test('should call api to associate role', async () => {
|
||||
JobTemplatesAPI.read.mockResolvedValue(resources);
|
||||
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
||||
UsersAPI.associateRole.mockResolvedValue({});
|
||||
|
||||
await act(async () =>
|
||||
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
||||
fetchItems: JobTemplatesAPI.read,
|
||||
fetchOptions: JobTemplatesAPI.readOptions,
|
||||
label: 'Job template',
|
||||
selectedResource: 'jobTemplate',
|
||||
searchColumns: [
|
||||
@ -169,6 +180,7 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
|
||||
test('should throw error', async () => {
|
||||
JobTemplatesAPI.read.mockResolvedValue(resources);
|
||||
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
||||
UsersAPI.associateRole.mockRejectedValue(
|
||||
new Error({
|
||||
response: {
|
||||
@ -192,6 +204,7 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
await act(async () =>
|
||||
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
||||
fetchItems: JobTemplatesAPI.read,
|
||||
fetchOptions: JobTemplatesAPI.readOptions,
|
||||
label: 'Job template',
|
||||
selectedResource: 'jobTemplate',
|
||||
searchColumns: [
|
||||
|
||||
@ -39,6 +39,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => JobTemplatesAPI.read(queryParams),
|
||||
fetchOptions: () => JobTemplatesAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'workflowJobTemplate',
|
||||
@ -69,6 +70,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => WorkflowJobTemplatesAPI.read(queryParams),
|
||||
fetchOptions: () => WorkflowJobTemplatesAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'credential',
|
||||
@ -110,6 +112,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => CredentialsAPI.read(queryParams),
|
||||
fetchOptions: () => CredentialsAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'inventory',
|
||||
@ -136,6 +139,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => InventoriesAPI.read(queryParams),
|
||||
fetchOptions: () => InventoriesAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'project',
|
||||
@ -177,6 +181,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => ProjectsAPI.read(queryParams),
|
||||
fetchOptions: () => ProjectsAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'organization',
|
||||
@ -203,6 +208,7 @@ export default function getResourceAccessConfig(i18n) {
|
||||
},
|
||||
],
|
||||
fetchItems: queryParams => OrganizationsAPI.read(queryParams),
|
||||
fetchOptions: () => OrganizationsAPI.readOptions(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -118,6 +118,11 @@ function HostGroupsList({ i18n, host }) {
|
||||
[invId, hostId]
|
||||
);
|
||||
|
||||
const fetchGroupsOptions = useCallback(
|
||||
() => InventoriesAPI.readGroupsOptions(invId),
|
||||
[invId]
|
||||
);
|
||||
|
||||
const { request: handleAssociate, error: associateError } = useRequest(
|
||||
useCallback(
|
||||
async groupsToAssociate => {
|
||||
@ -224,6 +229,7 @@ function HostGroupsList({ i18n, host }) {
|
||||
<AssociateModal
|
||||
header={i18n._(t`Groups`)}
|
||||
fetchRequest={fetchGroupsToAssociate}
|
||||
optionsRequest={fetchGroupsOptions}
|
||||
isModalOpen={isModalOpen}
|
||||
onAssociate={handleAssociate}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
||||
@ -207,6 +207,15 @@ describe('<HostGroupsList />', () => {
|
||||
results: [{ id: 123, name: 'foo', url: '/api/v2/groups/123/' }],
|
||||
},
|
||||
});
|
||||
InventoriesAPI.readGroupsOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper.find('ToolbarAddButton').simulate('click');
|
||||
});
|
||||
|
||||
@ -111,6 +111,11 @@ function InventoryGroupHostList({ i18n }) {
|
||||
[groupId, inventoryId]
|
||||
);
|
||||
|
||||
const fetchHostsOptions = useCallback(
|
||||
() => InventoriesAPI.readHostsOptions(inventoryId),
|
||||
[inventoryId]
|
||||
);
|
||||
|
||||
const { request: handleAssociate, error: associateErr } = useRequest(
|
||||
useCallback(
|
||||
async hostsToAssociate => {
|
||||
@ -227,6 +232,7 @@ function InventoryGroupHostList({ i18n }) {
|
||||
<AssociateModal
|
||||
header={i18n._(t`Hosts`)}
|
||||
fetchRequest={fetchHostsToAssociate}
|
||||
optionsRequest={fetchHostsOptions}
|
||||
isModalOpen={isModalOpen}
|
||||
onAssociate={handleAssociate}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
||||
@ -116,6 +116,11 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
[invId, hostId]
|
||||
);
|
||||
|
||||
const fetchGroupsOptions = useCallback(
|
||||
() => InventoriesAPI.readGroupsOptions(invId),
|
||||
[invId]
|
||||
);
|
||||
|
||||
const { request: handleAssociate, error: associateError } = useRequest(
|
||||
useCallback(
|
||||
async groupsToAssociate => {
|
||||
@ -221,6 +226,7 @@ function InventoryHostGroupsList({ i18n }) {
|
||||
<AssociateModal
|
||||
header={i18n._(t`Groups`)}
|
||||
fetchRequest={fetchGroupsToAssociate}
|
||||
optionsRequest={fetchGroupsOptions}
|
||||
isModalOpen={isModalOpen}
|
||||
onAssociate={handleAssociate}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
||||
@ -199,6 +199,15 @@ describe('<InventoryHostGroupsList />', () => {
|
||||
results: [{ id: 123, name: 'foo', url: '/api/v2/groups/123/' }],
|
||||
},
|
||||
});
|
||||
InventoriesAPI.readGroupsOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
GET: {},
|
||||
POST: {},
|
||||
},
|
||||
related_search_fields: [],
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper.find('ToolbarAddButton').simulate('click');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user