diff --git a/awx/ui_next/src/api/models/Credentials.js b/awx/ui_next/src/api/models/Credentials.js index ec7f97812d..95e954fc0c 100644 --- a/awx/ui_next/src/api/models/Credentials.js +++ b/awx/ui_next/src/api/models/Credentials.js @@ -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, diff --git a/awx/ui_next/src/api/models/Inventories.js b/awx/ui_next/src/api/models/Inventories.js index ab828e32d6..077a534d27 100644 --- a/awx/ui_next/src/api/models/Inventories.js +++ b/awx/ui_next/src/api/models/Inventories.js @@ -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); } diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index 0e2eba8079..4f631cec2a 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -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, diff --git a/awx/ui_next/src/api/models/Organizations.js b/awx/ui_next/src/api/models/Organizations.js index 267c9aba1e..e6f12a26a3 100644 --- a/awx/ui_next/src/api/models/Organizations.js +++ b/awx/ui_next/src/api/models/Organizations.js @@ -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); } diff --git a/awx/ui_next/src/api/models/Projects.js b/awx/ui_next/src/api/models/Projects.js index 269ef18f8a..38879a2bc2 100644 --- a/awx/ui_next/src/api/models/Projects.js +++ b/awx/ui_next/src/api/models/Projects.js @@ -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/`); } diff --git a/awx/ui_next/src/api/models/Teams.js b/awx/ui_next/src/api/models/Teams.js index 1a205993d4..180c59032c 100644 --- a/awx/ui_next/src/api/models/Teams.js +++ b/awx/ui_next/src/api/models/Teams.js @@ -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/`); } diff --git a/awx/ui_next/src/api/models/WorkflowJobTemplates.js b/awx/ui_next/src/api/models/WorkflowJobTemplates.js index 3074608796..6326ebecae 100644 --- a/awx/ui_next/src/api/models/WorkflowJobTemplates.js +++ b/awx/ui_next/src/api/models/WorkflowJobTemplates.js @@ -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/`); } diff --git a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx index ec60c553cd..dfc4e1391f 100644 --- a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx +++ b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx @@ -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 })} diff --git a/awx/ui_next/src/components/NotificationList/NotificationList.jsx b/awx/ui_next/src/components/NotificationList/NotificationList.jsx index b1bca91d37..882a304654 100644 --- a/awx/ui_next/src/components/NotificationList/NotificationList.jsx +++ b/awx/ui_next/src/components/NotificationList/NotificationList.jsx @@ -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 => ( { 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 => ( ', () => { 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 () => { diff --git a/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx b/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx index 3f3dcbdb1c..3ba0ec1fff 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx @@ -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 => ( { 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 => ( ', () => { beforeEach(() => { OrganizationsAPI.readTeams.mockResolvedValue(listData); + OrganizationsAPI.readTeamsOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); }); afterEach(() => { diff --git a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx index d804a6ea83..f22c393866 100644 --- a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx @@ -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 => ( { ], }, }); + 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(); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.jsx index 2ccdbef448..47e400f716 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.jsx @@ -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} /> ); } diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.test.jsx index 8725beff57..ad6fd57211 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.test.jsx @@ -38,6 +38,15 @@ describe('InventorySourcesList', () => { ], }, }); + InventorySourcesAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); await act(async () => { wrapper = mountWithContexts( { 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} /> ); } diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.jsx index 580e96d465..3b9fdec0e9 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.jsx @@ -38,6 +38,15 @@ describe('JobTemplatesList', () => { ], }, }); + JobTemplatesAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); await act(async () => { wrapper = mountWithContexts( { }); 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( { ], }, }); + 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(); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.jsx index e7dfa098c9..3a9b747e35 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.jsx @@ -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} /> ); } diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.test.jsx index eff4ccf517..765a329d3d 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.test.jsx @@ -38,6 +38,15 @@ describe('ProjectsList', () => { ], }, }); + ProjectsAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); await act(async () => { wrapper = mountWithContexts( { 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} /> ); } diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.test.jsx index f3bf00a1d9..121c62cce6 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.test.jsx @@ -38,6 +38,15 @@ describe('WorkflowJobTemplatesList', () => { ], }, }); + WorkflowJobTemplatesAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); await act(async () => { wrapper = mountWithContexts( 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 => (