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:
softwarefactory-project-zuul[bot] 2021-03-30 19:58:13 +00:00 committed by GitHub
commit 85b3d7c515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 371 additions and 5 deletions

View File

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

View File

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

View File

@ -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}
/>
</Route>
<Route path="/execution_environments/:id/templates">
<ExecutionEnvironmentTemplateList
executionEnvironment={executionEnvironment}
/>
</Route>
</>
)}
</Switch>

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
export { default } from './ExecutionEnvironmentTemplateList';

View File

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