mirror of
https://github.com/ansible/awx.git
synced 2026-03-06 11:11:07 -03:30
Merge pull request #9739 from nixocio/ui_issue_9723
Add templates screen to EE Add templates screen to EE. See: #9723 Reviewed-by: Jake McDermott <yo@jakemcdermott.me> Reviewed-by: Tiago Góes <tiago.goes2009@gmail.com>
This commit is contained in:
@@ -5,6 +5,16 @@ class ExecutionEnvironments extends Base {
|
|||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/execution_environments/';
|
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;
|
export default ExecutionEnvironments;
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readExecutionEnvironmentsOptions(id, params) {
|
readExecutionEnvironmentsOptions(id) {
|
||||||
return this.http.options(`${this.baseUrl}${id}/execution_environments/`, {
|
return this.http.options(`${this.baseUrl}${id}/execution_environments/`);
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createUser(id, data) {
|
createUser(id, data) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import ContentLoading from '../../components/ContentLoading';
|
|||||||
|
|
||||||
import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
|
import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
|
||||||
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
||||||
|
import ExecutionEnvironmentTemplateList from './ExecutionEnvironmentTemplate';
|
||||||
|
|
||||||
function ExecutionEnvironment({ i18n, setBreadcrumb }) {
|
function ExecutionEnvironment({ i18n, setBreadcrumb }) {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -64,6 +65,11 @@ function ExecutionEnvironment({ i18n, setBreadcrumb }) {
|
|||||||
link: `/execution_environments/${id}/details`,
|
link: `/execution_environments/${id}/details`,
|
||||||
id: 0,
|
id: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Templates`),
|
||||||
|
link: `/execution_environments/${id}/templates`,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isLoading && contentError) {
|
if (!isLoading && contentError) {
|
||||||
@@ -114,6 +120,11 @@ function ExecutionEnvironment({ i18n, setBreadcrumb }) {
|
|||||||
executionEnvironment={executionEnvironment}
|
executionEnvironment={executionEnvironment}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/execution_environments/:id/templates">
|
||||||
|
<ExecutionEnvironmentTemplateList
|
||||||
|
executionEnvironment={executionEnvironment}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<Card>
|
||||||
|
<PaginatedDataList
|
||||||
|
contentError={contentError}
|
||||||
|
hasContentLoading={isLoading}
|
||||||
|
items={templates}
|
||||||
|
itemCount={templatesCount}
|
||||||
|
pluralizedItemName={i18n._(t`Templates`)}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
toolbarSearchableKeys={searchableKeys}
|
||||||
|
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||||
|
toolbarSearchColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name__icontains',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Type`),
|
||||||
|
key: 'or__type',
|
||||||
|
options: [
|
||||||
|
[`job_template`, i18n._(t`Job Template`)],
|
||||||
|
[`workflow_job_template`, i18n._(t`Workflow Template`)],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created By (Username)`),
|
||||||
|
key: 'created_by__username__icontains',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified By (Username)`),
|
||||||
|
key: 'modified_by__username__icontains',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
toolbarSortColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created`),
|
||||||
|
key: 'created',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderToolbar={props => (
|
||||||
|
<DatalistToolbar {...props} qsConfig={QS_CONFIG} />
|
||||||
|
)}
|
||||||
|
renderItem={template => (
|
||||||
|
<ExecutionEnvironmentTemplateListItem
|
||||||
|
key={template.id}
|
||||||
|
template={template}
|
||||||
|
detailUrl={`/templates/${template.type}/${template.id}/details`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(ExecutionEnvironmentTemplateList);
|
||||||
@@ -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('<ExecutionEnvironmentTemplateList/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
test('should mount successfully', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ExecutionEnvironmentTemplateList
|
||||||
|
executionEnvironment={mockExecutionEnvironment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
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(
|
||||||
|
<ExecutionEnvironmentTemplateList
|
||||||
|
executionEnvironment={mockExecutionEnvironment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
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(
|
||||||
|
<ExecutionEnvironmentTemplateList
|
||||||
|
executionEnvironment={mockExecutionEnvironment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'ExecutionEnvironmentTemplateList',
|
||||||
|
el => el.length > 0
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ToolbarAddButton').length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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 (
|
||||||
|
<DataListItem
|
||||||
|
key={template.id}
|
||||||
|
aria-labelledby={`check-action-${template.id}`}
|
||||||
|
id={`${template.id}`}
|
||||||
|
>
|
||||||
|
<DataListItemRow>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell key="name" aria-label={i18n._(t`Name`)}>
|
||||||
|
<Link to={`${detailUrl}`}>
|
||||||
|
<b>{template.name}</b>
|
||||||
|
</Link>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell
|
||||||
|
key="template-type"
|
||||||
|
aria-label={i18n._(t`Template type`)}
|
||||||
|
>
|
||||||
|
{template.type === 'job_template'
|
||||||
|
? i18n._(t`Job Template`)
|
||||||
|
: i18n._(t`Workflow Job Template`)}
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(ExecutionEnvironmentTemplateListItem);
|
||||||
@@ -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('<ExecutionEnvironmentTemplateListItem/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
const template = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Foo',
|
||||||
|
type: 'job_template',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should mount successfully', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ExecutionEnvironmentTemplateListItem
|
||||||
|
template={template}
|
||||||
|
detailUrl={`/templates/${template.type}/${template.id}/details`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
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(
|
||||||
|
<ExecutionEnvironmentTemplateListItem
|
||||||
|
template={{ ...template, type: 'workflow_job_template' }}
|
||||||
|
detailUrl={`/templates/${template.type}/${template.id}/details`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('DataListCell[aria-label="Template type"]').text()
|
||||||
|
).toBe('Workflow Job Template');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ExecutionEnvironmentTemplateList';
|
||||||
@@ -38,7 +38,7 @@ function OrganizationExecEnvList({ i18n, organization }) {
|
|||||||
|
|
||||||
const [response, responseActions] = await Promise.all([
|
const [response, responseActions] = await Promise.all([
|
||||||
OrganizationsAPI.readExecutionEnvironments(id, params),
|
OrganizationsAPI.readExecutionEnvironments(id, params),
|
||||||
OrganizationsAPI.readExecutionEnvironmentsOptions(id, params),
|
OrganizationsAPI.readExecutionEnvironmentsOptions(id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user