Merge pull request #7903 from jlmitch5/searchableKeysPt3

update newly useRequested lists to get advanced searchableKeys

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-08-20 19:59:54 +00:00 committed by GitHub
commit bfe00d46ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 502 additions and 53 deletions

View File

@ -6,6 +6,7 @@ class Credentials extends Base {
this.baseUrl = '/api/v2/credentials/';
this.readAccessList = this.readAccessList.bind(this);
this.readAccessOptions = this.readAccessOptions.bind(this);
this.readInputSources = this.readInputSources.bind(this);
}
@ -15,6 +16,10 @@ class Credentials extends Base {
});
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readInputSources(id, params) {
return this.http.get(`${this.baseUrl}${id}/input_sources/`, {
params,

View File

@ -7,6 +7,7 @@ class Inventories extends InstanceGroupsMixin(Base) {
this.baseUrl = '/api/v2/inventories/';
this.readAccessList = this.readAccessList.bind(this);
this.readAccessOptions = this.readAccessOptions.bind(this);
this.readHosts = this.readHosts.bind(this);
this.readHostDetail = this.readHostDetail.bind(this);
this.readGroups = this.readGroups.bind(this);
@ -20,6 +21,10 @@ class Inventories extends InstanceGroupsMixin(Base) {
});
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
createHost(id, data) {
return this.http.post(`${this.baseUrl}${id}/hosts/`, data);
}

View File

@ -16,6 +16,7 @@ class JobTemplates extends SchedulesMixin(
this.disassociateLabel = this.disassociateLabel.bind(this);
this.readCredentials = this.readCredentials.bind(this);
this.readAccessList = this.readAccessList.bind(this);
this.readAccessOptions = this.readAccessOptions.bind(this);
this.readWebhookKey = this.readWebhookKey.bind(this);
}
@ -66,6 +67,10 @@ class JobTemplates extends SchedulesMixin(
});
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readScheduleList(id, params) {
return this.http.get(`${this.baseUrl}${id}/schedules/`, {
params,

View File

@ -12,10 +12,18 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readTeams(id, params) {
return this.http.get(`${this.baseUrl}${id}/teams/`, { params });
}
readTeamsOptions(id) {
return this.http.options(`${this.baseUrl}${id}/teams/`);
}
createUser(id, data) {
return this.http.post(`${this.baseUrl}${id}/users/`, data);
}

View File

@ -11,6 +11,7 @@ class Projects extends SchedulesMixin(
this.baseUrl = '/api/v2/projects/';
this.readAccessList = this.readAccessList.bind(this);
this.readAccessOptions = this.readAccessOptions.bind(this);
this.readInventories = this.readInventories.bind(this);
this.readPlaybooks = this.readPlaybooks.bind(this);
this.readSync = this.readSync.bind(this);
@ -21,6 +22,10 @@ class Projects extends SchedulesMixin(
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readInventories(id) {
return this.http.get(`${this.baseUrl}${id}/inventories/`);
}

View File

@ -35,6 +35,10 @@ class Teams extends Base {
});
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readUsersAccessOptions(teamId) {
return this.http.options(`${this.baseUrl}${teamId}/users/`);
}

View File

@ -54,6 +54,10 @@ class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) {
});
}
readAccessOptions(id) {
return this.http.options(`${this.baseUrl}${id}/access_list/`);
}
readSurvey(id) {
return this.http.get(`${this.baseUrl}${id}/survey_spec/`);
}

View File

@ -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}
/>

View File

@ -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)}

View File

@ -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={[]}
/>
);

View File

@ -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>

View File

@ -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
/>
);

View File

@ -29,21 +29,32 @@ function OrganizationLookup({
history,
}) {
const {
result: { itemCount, organizations },
result: { itemCount, organizations, relatedSearchableKeys, searchableKeys },
error: contentError,
request: fetchOrganizations,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search);
const { data } = await OrganizationsAPI.read(params);
const [response, actionsResponse] = await Promise.all([
OrganizationsAPI.read(params),
OrganizationsAPI.readOptions(),
]);
return {
organizations: data.results,
itemCount: data.count,
organizations: response.data.results,
itemCount: response.data.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),
};
}, [history.location.search]),
{
organizations: [],
itemCount: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -98,6 +109,8 @@ function OrganizationLookup({
key: 'name',
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
readOnly={!canDelete}
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}

View File

@ -23,7 +23,7 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
const [toggleError, setToggleError] = useState(null);
const {
result: fetchNotificationsResult,
result: fetchNotificationsResults,
result: {
notifications,
itemCount,
@ -31,6 +31,8 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
successTemplateIds,
errorTemplateIds,
typeLabels,
relatedSearchableKeys,
searchableKeys,
},
error: contentError,
isLoading,
@ -43,15 +45,13 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
{
data: { results: notificationsResults, count: notificationsCount },
},
{
data: { actions },
},
actionsResponse,
] = await Promise.all([
NotificationTemplatesAPI.read(params),
NotificationTemplatesAPI.readOptions(),
]);
const labels = actions.GET.notification_type.choices.reduce(
const labels = actionsResponse.data.actions.GET.notification_type.choices.reduce(
(map, notifType) => ({ ...map, [notifType[0]]: notifType[1] }),
{}
);
@ -78,6 +78,12 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
successTemplateIds: successTemplates.results.map(su => su.id),
errorTemplateIds: errorTemplates.results.map(e => e.id),
typeLabels: labels,
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),
};
}, [apiModel, id, location]),
{
@ -87,6 +93,8 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
successTemplateIds: [],
errorTemplateIds: [],
typeLabels: {},
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -108,8 +116,8 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
status
);
setValue({
...fetchNotificationsResult,
[`${status}TemplateIds`]: fetchNotificationsResult[
...fetchNotificationsResults,
[`${status}TemplateIds`]: fetchNotificationsResults[
`${status}TemplateIds`
].filter(i => i !== notificationId),
});
@ -120,8 +128,8 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
status
);
setValue({
...fetchNotificationsResult,
[`${status}TemplateIds`]: fetchNotificationsResult[
...fetchNotificationsResults,
[`${status}TemplateIds`]: fetchNotificationsResults[
`${status}TemplateIds`
].concat(notificationId),
});
@ -179,6 +187,8 @@ function NotificationList({ apiModel, canToggleNotifications, id, i18n }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={notification => (
<NotificationListItem
key={notification.id}

View File

@ -26,22 +26,33 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
const location = useLocation();
const {
result: { accessRecords, itemCount },
result: { accessRecords, itemCount, relatedSearchableKeys, searchableKeys },
error: contentError,
isLoading,
request: fetchAccessRecords,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const response = await apiModel.readAccessList(resource.id, params);
const [response, actionsResponse] = await Promise.all([
apiModel.readAccessList(resource.id, params),
apiModel.readAccessOptions(resource.id),
]);
return {
accessRecords: response.data.results,
itemCount: response.data.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),
};
}, [apiModel, location, resource.id]),
{
accessRecords: [],
itemCount: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -106,6 +117,8 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
key: 'last_name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => (
<DataListToolbar
{...props}

View File

@ -76,6 +76,15 @@ describe('<ResourceAccessList />', () => {
beforeEach(async () => {
OrganizationsAPI.readAccessList.mockResolvedValue({ data });
OrganizationsAPI.readAccessOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
TeamsAPI.disassociateRole.mockResolvedValue({});
UsersAPI.disassociateRole.mockResolvedValue({});
await act(async () => {

View File

@ -96,6 +96,7 @@ function UserAndTeamAccessAdd({
displayKey="name"
onRowClick={handleResourceSelect}
fetchItems={selectedResourceType.fetchItems}
fetchOptions={selectedResourceType.fetchOptions}
selectedLabel={i18n._(t`Selected`)}
selectedResourceRows={resourcesSelected}
sortedColumnKey="username"

View File

@ -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: [

View File

@ -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(),
},
];
}

View File

@ -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)}

View File

@ -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');
});

View File

@ -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)}

View File

@ -47,7 +47,13 @@ function InventoryGroupsList({ i18n }) {
const { id: inventoryId } = useParams();
const {
result: { groups, groupCount, actions },
result: {
groups,
groupCount,
actions,
relatedSearchableKeys,
searchableKeys,
},
error: contentError,
isLoading,
request: fetchGroups,
@ -62,12 +68,20 @@ function InventoryGroupsList({ i18n }) {
groups: response.data.results,
groupCount: response.data.count,
actions: actionsResponse.data.actions,
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),
};
}, [inventoryId, location]),
{
groups: [],
groupCount: 0,
actions: {},
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -168,6 +182,8 @@ function InventoryGroupsList({ i18n }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={item => (
<InventoryGroupItem
key={item.id}

View File

@ -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)}

View File

@ -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');
});

View File

@ -19,22 +19,33 @@ function OrganizationTeamList({ id, i18n }) {
const location = useLocation();
const {
result: { teams, count },
result: { teams, count, relatedSearchableKeys, searchableKeys },
error,
isLoading,
request: fetchTeams,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const results = await OrganizationsAPI.readTeams(id, params);
const [response, actionsResponse] = await Promise.all([
OrganizationsAPI.readTeams(id, params),
OrganizationsAPI.readTeamsOptions(id),
]);
return {
teams: results.data.results,
count: results.data.count,
teams: response.data.results,
count: response.data.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),
};
}, [id, location]),
{
teams: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -71,6 +82,8 @@ function OrganizationTeamList({ id, i18n }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={item => (
<OrganizationTeamListItem
key={item.id}

View File

@ -53,6 +53,15 @@ const listData = {
describe('<OrganizationTeamList />', () => {
beforeEach(() => {
OrganizationsAPI.readTeams.mockResolvedValue(listData);
OrganizationsAPI.readTeamsOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
});
afterEach(() => {

View File

@ -27,7 +27,13 @@ function ProjectJobTemplatesList({ i18n }) {
const location = useLocation();
const {
result: { jobTemplates, itemCount, actions },
result: {
jobTemplates,
itemCount,
actions,
relatedSearchableKeys,
searchableKeys,
},
error: contentError,
isLoading,
request: fetchTemplates,
@ -43,12 +49,20 @@ function ProjectJobTemplatesList({ i18n }) {
jobTemplates: response.data.results,
itemCount: response.data.count,
actions: actionsResponse.data.actions,
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, projectId]),
{
jobTemplates: [],
itemCount: 0,
actions: {},
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -142,6 +156,8 @@ function ProjectJobTemplatesList({ i18n }) {
key: 'type',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => (
<DatalistToolbar
{...props}

View File

@ -40,6 +40,15 @@ describe('NodeModal', () => {
],
},
});
JobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
ProjectsAPI.read.mockResolvedValue({
data: {
count: 1,
@ -53,6 +62,15 @@ describe('NodeModal', () => {
],
},
});
ProjectsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
InventorySourcesAPI.read.mockResolvedValue({
data: {
count: 1,
@ -66,6 +84,15 @@ describe('NodeModal', () => {
],
},
});
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
WorkflowJobTemplatesAPI.read.mockResolvedValue({
data: {
count: 1,
@ -79,6 +106,15 @@ describe('NodeModal', () => {
],
},
});
WorkflowJobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
});
afterAll(() => {
jest.clearAllMocks();

View File

@ -20,22 +20,33 @@ function InventorySourcesList({ i18n, nodeResource, onUpdateNodeResource }) {
const location = useLocation();
const {
result: { inventorySources, count },
result: { inventorySources, count, relatedSearchableKeys, searchableKeys },
error,
isLoading,
request: fetchInventorySources,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const results = await InventorySourcesAPI.read(params);
const [response, actionsResponse] = await Promise.all([
InventorySourcesAPI.read(params),
InventorySourcesAPI.readOptions(),
]);
return {
inventorySources: results.data.results,
count: results.data.count,
inventorySources: response.data.results,
count: response.data.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]),
{
inventorySources: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -94,6 +105,8 @@ function InventorySourcesList({ i18n, nodeResource, onUpdateNodeResource }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
);
}

View File

@ -38,6 +38,15 @@ describe('InventorySourcesList', () => {
],
},
});
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<InventorySourcesList

View File

@ -20,24 +20,35 @@ function JobTemplatesList({ i18n, nodeResource, onUpdateNodeResource }) {
const location = useLocation();
const {
result: { jobTemplates, count },
result: { jobTemplates, count, relatedSearchableKeys, searchableKeys },
error,
isLoading,
request: fetchJobTemplates,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const results = await JobTemplatesAPI.read(params, {
role_level: 'execute_role',
});
const [response, actionsResponse] = await Promise.all([
JobTemplatesAPI.read(params, {
role_level: 'execute_role',
}),
JobTemplatesAPI.readOptions(),
]);
return {
jobTemplates: results.data.results,
count: results.data.count,
jobTemplates: response.data.results,
count: response.data.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]),
{
jobTemplates: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -92,6 +103,8 @@ function JobTemplatesList({ i18n, nodeResource, onUpdateNodeResource }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
);
}

View File

@ -38,6 +38,15 @@ describe('JobTemplatesList', () => {
],
},
});
JobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<JobTemplatesList
@ -67,6 +76,15 @@ describe('JobTemplatesList', () => {
});
test('Error shown when read() request errors', async () => {
JobTemplatesAPI.read.mockRejectedValue(new Error());
JobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<JobTemplatesList

View File

@ -35,6 +35,15 @@ describe('NodeTypeStep', () => {
],
},
});
JobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
ProjectsAPI.read.mockResolvedValue({
data: {
count: 1,
@ -48,6 +57,15 @@ describe('NodeTypeStep', () => {
],
},
});
ProjectsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
InventorySourcesAPI.read.mockResolvedValue({
data: {
count: 1,
@ -61,6 +79,15 @@ describe('NodeTypeStep', () => {
],
},
});
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
WorkflowJobTemplatesAPI.read.mockResolvedValue({
data: {
count: 1,
@ -74,6 +101,15 @@ describe('NodeTypeStep', () => {
],
},
});
WorkflowJobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
});
afterAll(() => {
jest.clearAllMocks();

View File

@ -20,22 +20,33 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
const location = useLocation();
const {
result: { projects, count },
result: { projects, count, relatedSearchableKeys, searchableKeys },
error,
isLoading,
request: fetchProjects,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const results = await ProjectsAPI.read(params);
const [response, actionsResponse] = await Promise.all([
ProjectsAPI.read(params),
ProjectsAPI.readOptions(),
]);
return {
projects: results.data.results,
count: results.data.count,
projects: response.data.results,
count: response.data.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]),
{
projects: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -101,6 +112,8 @@ function ProjectsList({ i18n, nodeResource, onUpdateNodeResource }) {
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
);
}

View File

@ -38,6 +38,15 @@ describe('ProjectsList', () => {
],
},
});
ProjectsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<ProjectsList

View File

@ -24,24 +24,40 @@ function WorkflowJobTemplatesList({
const location = useLocation();
const {
result: { workflowJobTemplates, count },
result: {
workflowJobTemplates,
count,
relatedSearchableKeys,
searchableKeys,
},
error,
isLoading,
request: fetchWorkflowJobTemplates,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const results = await WorkflowJobTemplatesAPI.read(params, {
role_level: 'execute_role',
});
const [response, actionsResponse] = await Promise.all([
WorkflowJobTemplatesAPI.read(params, {
role_level: 'execute_role',
}),
WorkflowJobTemplatesAPI.readOptions(),
]);
return {
workflowJobTemplates: results.data.results,
count: results.data.count,
workflowJobTemplates: response.data.results,
count: response.data.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]),
{
workflowJobTemplates: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -100,6 +116,8 @@ function WorkflowJobTemplatesList({
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
);
}

View File

@ -38,6 +38,15 @@ describe('WorkflowJobTemplatesList', () => {
],
},
});
WorkflowJobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<WorkflowJobTemplatesList

View File

@ -27,7 +27,13 @@ function UserList({ i18n }) {
const match = useRouteMatch();
const {
result: { users, itemCount, actions },
result: {
users,
itemCount,
actions,
relatedSearchableKeys,
searchableKeys,
},
error: contentError,
isLoading,
request: fetchUsers,
@ -42,12 +48,20 @@ function UserList({ i18n }) {
users: response.data.results,
itemCount: response.data.count,
actions: actionsResponse.data.actions,
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]),
{
users: [],
itemCount: 0,
actions: {},
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -124,6 +138,8 @@ function UserList({ i18n }) {
key: 'last_name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => (
<DataListToolbar
{...props}