From 115a344842f9f6811ad096ec97c11f5cf6922317 Mon Sep 17 00:00:00 2001 From: nixocio Date: Mon, 29 Mar 2021 10:35:22 -0400 Subject: [PATCH] Add templates screen to EE Add templates screen to EE. See: https://github.com/ansible/awx/issues/9723 --- .../src/api/models/ExecutionEnvironments.js | 10 ++ awx/ui_next/src/api/models/Organizations.js | 6 +- .../ExecutionEnvironment.jsx | 11 ++ .../ExecutionEnvironmentTemplateList.jsx | 139 ++++++++++++++++++ .../ExecutionEnvironmentTemplateList.test.jsx | 116 +++++++++++++++ .../ExecutionEnvironmentTemplateListItem.jsx | 43 ++++++ ...cutionEnvironmentTemplateListItem.test.jsx | 48 ++++++ .../ExecutionEnvironmentTemplate/index.js | 1 + .../OrganizationExecEnvList.jsx | 2 +- 9 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.jsx create mode 100644 awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.jsx create mode 100644 awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.jsx create mode 100644 awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.jsx create mode 100644 awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js diff --git a/awx/ui_next/src/api/models/ExecutionEnvironments.js b/awx/ui_next/src/api/models/ExecutionEnvironments.js index 2df933d53a..ae3d128ed3 100644 --- a/awx/ui_next/src/api/models/ExecutionEnvironments.js +++ b/awx/ui_next/src/api/models/ExecutionEnvironments.js @@ -5,6 +5,16 @@ class ExecutionEnvironments extends Base { super(http); this.baseUrl = '/api/v2/execution_environments/'; } + + readUnifiedJobTemplates(id, params) { + return this.http.get(`${this.baseUrl}${id}/unified_job_templates/`, { + params, + }); + } + + readUnifiedJobTemplateOptions(id) { + return this.http.options(`${this.baseUrl}${id}/unified_job_templates/`); + } } export default ExecutionEnvironments; diff --git a/awx/ui_next/src/api/models/Organizations.js b/awx/ui_next/src/api/models/Organizations.js index fd980fece8..a2baa4f9c8 100644 --- a/awx/ui_next/src/api/models/Organizations.js +++ b/awx/ui_next/src/api/models/Organizations.js @@ -36,10 +36,8 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) { }); } - readExecutionEnvironmentsOptions(id, params) { - return this.http.options(`${this.baseUrl}${id}/execution_environments/`, { - params, - }); + readExecutionEnvironmentsOptions(id) { + return this.http.options(`${this.baseUrl}${id}/execution_environments/`); } createUser(id, data) { diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx index 55a3228e13..bb86dc2f57 100644 --- a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironment.jsx @@ -20,6 +20,7 @@ import ContentLoading from '../../components/ContentLoading'; import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails'; import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit'; +import ExecutionEnvironmentTemplateList from './ExecutionEnvironmentTemplate'; function ExecutionEnvironment({ i18n, setBreadcrumb }) { const { id } = useParams(); @@ -64,6 +65,11 @@ function ExecutionEnvironment({ i18n, setBreadcrumb }) { link: `/execution_environments/${id}/details`, id: 0, }, + { + name: i18n._(t`Templates`), + link: `/execution_environments/${id}/templates`, + id: 1, + }, ]; if (!isLoading && contentError) { @@ -114,6 +120,11 @@ function ExecutionEnvironment({ i18n, setBreadcrumb }) { executionEnvironment={executionEnvironment} /> + + + )} diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.jsx new file mode 100644 index 0000000000..c2b36eee30 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.jsx @@ -0,0 +1,139 @@ +import React, { useEffect, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Card } from '@patternfly/react-core'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import { getQSConfig, parseQueryString } from '../../../util/qs'; +import useRequest from '../../../util/useRequest'; +import DatalistToolbar from '../../../components/DataListToolbar'; +import PaginatedDataList from '../../../components/PaginatedDataList'; + +import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem'; + +const QS_CONFIG = getQSConfig( + 'execution_environments', + { + page: 1, + page_size: 20, + order_by: 'name', + type: 'job_template,workflow_job_template', + }, + ['id', 'page', 'page_size'] +); + +function ExecutionEnvironmentTemplateList({ i18n, executionEnvironment }) { + const { id } = executionEnvironment; + const location = useLocation(); + + const { + error: contentError, + isLoading, + request: fetchTemplates, + result: { + templates, + templatesCount, + relatedSearchableKeys, + searchableKeys, + }, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); + + const [response, responseActions] = await Promise.all([ + ExecutionEnvironmentsAPI.readUnifiedJobTemplates(id, params), + ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions(id), + ]); + + return { + templates: response.data.results, + templatesCount: response.data.count, + actions: responseActions.data.actions, + relatedSearchableKeys: ( + responseActions?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + responseActions.data.actions?.GET || {} + ).filter(key => responseActions.data.actions?.GET[key].filterable), + }; + }, [location, id]), + { + templates: [], + templatesCount: 0, + actions: {}, + relatedSearchableKeys: [], + searchableKeys: [], + } + ); + + useEffect(() => { + fetchTemplates(); + }, [fetchTemplates]); + + return ( + <> + + ( + + )} + renderItem={template => ( + + )} + /> + + + ); +} + +export default withI18n()(ExecutionEnvironmentTemplateList); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.jsx new file mode 100644 index 0000000000..078d6d249d --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { + mountWithContexts, + waitForElement, +} from '../../../../testUtils/enzymeHelpers'; + +import { ExecutionEnvironmentsAPI } from '../../../api'; +import ExecutionEnvironmentTemplateList from './ExecutionEnvironmentTemplateList'; + +jest.mock('../../../api/'); + +const templates = { + data: { + count: 3, + results: [ + { + id: 1, + type: 'job_template', + name: 'Foo', + url: '/api/v2/job_templates/1/', + related: { + execution_environment: '/api/v2/execution_environments/1/', + }, + }, + { + id: 2, + type: 'workflow_job_template', + name: 'Bar', + url: '/api/v2/workflow_job_templates/2/', + related: { + execution_environment: '/api/v2/execution_environments/1/', + }, + }, + { + id: 3, + type: 'job_template', + name: 'Fuzz', + url: '/api/v2/job_templates/3/', + related: { + execution_environment: '/api/v2/execution_environments/1/', + }, + }, + ], + }, +}; + +const mockExecutionEnvironment = { + id: 1, + name: 'Default EE', +}; + +const options = { data: { actions: { GET: {} } } }; + +describe('', () => { + let wrapper; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentTemplateList', + el => el.length > 0 + ); + }); + + test('should have data fetched and render 3 rows', async () => { + ExecutionEnvironmentsAPI.readUnifiedJobTemplates.mockResolvedValue( + templates + ); + + ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions.mockResolvedValue( + options + ); + + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement( + wrapper, + 'ExecutionEnvironmentTemplateList', + el => el.length > 0 + ); + + expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(3); + expect(ExecutionEnvironmentsAPI.readUnifiedJobTemplates).toBeCalled(); + expect(ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions).toBeCalled(); + }); + + test('should not render add button', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + waitForElement( + wrapper, + 'ExecutionEnvironmentTemplateList', + el => el.length > 0 + ); + expect(wrapper.find('ToolbarAddButton').length).toBe(0); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.jsx new file mode 100644 index 0000000000..4a33126386 --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Link } from 'react-router-dom'; +import { + DataListItem, + DataListItemRow, + DataListItemCells, +} from '@patternfly/react-core'; + +import DataListCell from '../../../components/DataListCell'; + +function ExecutionEnvironmentTemplateListItem({ template, detailUrl, i18n }) { + return ( + + + + + {template.name} + + , + + {template.type === 'job_template' + ? i18n._(t`Job Template`) + : i18n._(t`Workflow Job Template`)} + , + ]} + /> + + + ); +} + +export default withI18n()(ExecutionEnvironmentTemplateListItem); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.jsx new file mode 100644 index 0000000000..9c107ab19b --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; + +import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem'; + +describe('', () => { + let wrapper; + const template = { + id: 1, + name: 'Foo', + type: 'job_template', + }; + + test('should mount successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1); + expect(wrapper.find('DataListCell[aria-label="Name"]').text()).toBe( + template.name + ); + expect( + wrapper.find('DataListCell[aria-label="Template type"]').text() + ).toBe('Job Template'); + }); + + test('should distinguish template types', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1); + expect( + wrapper.find('DataListCell[aria-label="Template type"]').text() + ).toBe('Workflow Job Template'); + }); +}); diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js new file mode 100644 index 0000000000..3bdea254ee --- /dev/null +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js @@ -0,0 +1 @@ +export { default } from './ExecutionEnvironmentTemplateList'; diff --git a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx index 9f2c4ae817..d567c24097 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.jsx @@ -38,7 +38,7 @@ function OrganizationExecEnvList({ i18n, organization }) { const [response, responseActions] = await Promise.all([ OrganizationsAPI.readExecutionEnvironments(id, params), - OrganizationsAPI.readExecutionEnvironmentsOptions(id, params), + OrganizationsAPI.readExecutionEnvironmentsOptions(id), ]); return {