mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 01:47:31 -02:30
Fixes naviation bug by create ProjectTemplateList and ProjectTemplateListItem
Adds tests for those new files and removes uncessary test from TemplateListItem
This commit is contained in:
@@ -18,7 +18,7 @@ import PaginatedDataList, {
|
|||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
import AddDropDownButton from '@components/AddDropDownButton';
|
import AddDropDownButton from '@components/AddDropDownButton';
|
||||||
import ProjectTemplatesListItem from '../../Template/TemplateList/TemplateListItem';
|
import ProjectTemplatesListItem from './ProjectJobTemplatesListItem';
|
||||||
|
|
||||||
// The type value in const QS_CONFIG below does not have a space between job_template and
|
// The type value in const QS_CONFIG below does not have a space between job_template and
|
||||||
// workflow_job_template so the params sent to the API match what the api expects.
|
// workflow_job_template so the params sent to the API match what the api expects.
|
||||||
@@ -238,7 +238,7 @@ function ProjectJobTemplatesList({ i18n }) {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName="Templates"
|
pluralizedItemName="Templates"
|
||||||
/>,
|
/>,
|
||||||
(canAddJT || canAddWFJT) && addButton,
|
...(canAddJT || canAddWFJT ? [addButton] : []),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DataListAction as _DataListAction,
|
||||||
|
DataListCell,
|
||||||
|
DataListCheck,
|
||||||
|
DataListItem,
|
||||||
|
DataListItemRow,
|
||||||
|
DataListItemCells,
|
||||||
|
Tooltip,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import {
|
||||||
|
ExclamationTriangleIcon,
|
||||||
|
PencilAltIcon,
|
||||||
|
RocketIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
import LaunchButton from '@components/LaunchButton';
|
||||||
|
import Sparkline from '@components/Sparkline';
|
||||||
|
import { toTitleCase } from '@util/strings';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const DataListAction = styled(_DataListAction)`
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 16px;
|
||||||
|
grid-template-columns: repeat(2, 40px);
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ProjectJobTemplateListItem({
|
||||||
|
i18n,
|
||||||
|
template,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
detailUrl,
|
||||||
|
}) {
|
||||||
|
const labelId = `check-action-${template.id}`;
|
||||||
|
const canLaunch = template.summary_fields.user_capabilities.start;
|
||||||
|
|
||||||
|
const missingResourceIcon =
|
||||||
|
template.type === 'job_template' &&
|
||||||
|
(!template.summary_fields.project ||
|
||||||
|
(!template.summary_fields.inventory &&
|
||||||
|
!template.ask_inventory_on_launch));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
||||||
|
<DataListItemRow>
|
||||||
|
<DataListCheck
|
||||||
|
id={`select-jobTemplate-${template.id}`}
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={onSelect}
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
/>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell key="divider">
|
||||||
|
<span>
|
||||||
|
<Link to={`${detailUrl}`}>
|
||||||
|
<b>{template.name}</b>
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
{missingResourceIcon && (
|
||||||
|
<span>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Resources are missing from this template.`
|
||||||
|
)}
|
||||||
|
position="right"
|
||||||
|
>
|
||||||
|
<ExclamationTriangleIcon css="color: #c9190b; margin-left: 20px;" />
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key="type">
|
||||||
|
{toTitleCase(template.type)}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key="sparkline">
|
||||||
|
<Sparkline jobs={template.summary_fields.recent_jobs} />
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<DataListAction
|
||||||
|
aria-label="actions"
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
id={labelId}
|
||||||
|
>
|
||||||
|
{canLaunch && template.type === 'job_template' && (
|
||||||
|
<Tooltip content={i18n._(t`Launch Template`)} position="top">
|
||||||
|
<LaunchButton resource={template}>
|
||||||
|
{({ handleLaunch }) => (
|
||||||
|
<Button
|
||||||
|
css="grid-column: 1"
|
||||||
|
variant="plain"
|
||||||
|
onClick={handleLaunch}
|
||||||
|
>
|
||||||
|
<RocketIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</LaunchButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{template.summary_fields.user_capabilities.edit && (
|
||||||
|
<Tooltip content={i18n._(t`Edit Template`)} position="top">
|
||||||
|
<Button
|
||||||
|
css="grid-column: 2"
|
||||||
|
variant="plain"
|
||||||
|
component={Link}
|
||||||
|
to={`/templates/${template.type}/${template.id}/edit`}
|
||||||
|
>
|
||||||
|
<PencilAltIcon />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</DataListAction>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ProjectJobTemplateListItem as _ProjectJobTemplateListItem };
|
||||||
|
export default withI18n()(ProjectJobTemplateListItem);
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import ProjectJobTemplatesListItem from './ProjectJobTemplatesListItem';
|
||||||
|
|
||||||
|
describe('<ProjectJobTemplatesListItem />', () => {
|
||||||
|
test('launch button shown to users with start capabilities', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
start: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('LaunchButton').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
test('launch button hidden from users without start capabilities', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
start: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('LaunchButton').exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
test('edit button shown to users with edit capabilities', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
test('edit button hidden from users without edit capabilities', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
test('missing resource icon is shown.', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
test('missing resource icon is not shown when there is a project and an inventory.', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
project: { name: 'Foo', id: 2 },
|
||||||
|
inventory: { name: 'Bar', id: 2 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'job_template',
|
||||||
|
ask_inventory_on_launch: true,
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
project: { name: 'Foo', id: 2 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
test('missing resource icon is not shown type is workflow_job_template', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
template={{
|
||||||
|
id: 1,
|
||||||
|
name: 'Template 1',
|
||||||
|
url: '/templates/job_template/1',
|
||||||
|
type: 'workflow_job_template',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
|
||||||
|
});
|
||||||
|
test('clicking on template from project templates list navigates properly', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/projects/1/job_templates'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<ProjectJobTemplatesListItem
|
||||||
|
isSelected={false}
|
||||||
|
detailUrl="/templates/job_template/2/details"
|
||||||
|
template={{
|
||||||
|
id: 2,
|
||||||
|
name: 'Template 2',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
wrapper.find('Link').simulate('click', { button: 0 });
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/templates/job_template/2/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DataListAction as _DataListAction,
|
DataListAction as _DataListAction,
|
||||||
@@ -40,12 +40,6 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
|
|||||||
(!template.summary_fields.inventory &&
|
(!template.summary_fields.inventory &&
|
||||||
!template.ask_inventory_on_launch));
|
!template.ask_inventory_on_launch));
|
||||||
|
|
||||||
// const location = useLocation();
|
|
||||||
|
|
||||||
// if (location.pathname.startsWith('/projects')) {
|
|
||||||
// detailUrl = `/templates/job_template/${template.id}/details`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
||||||
<DataListItemRow>
|
<DataListItemRow>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
@@ -167,83 +166,24 @@ describe('<TemplateListItem />', () => {
|
|||||||
initialEntries: ['/templates'],
|
initialEntries: ['/templates'],
|
||||||
});
|
});
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<Route
|
<TemplateListItem
|
||||||
path="/templates"
|
isSelected={false}
|
||||||
component={() => (
|
detailUrl="/templates/job_template/1/details"
|
||||||
<TemplateListItem
|
template={{
|
||||||
isSelected={false}
|
id: 1,
|
||||||
detailUrl="/templates/job_template/1/details"
|
name: 'Template 1',
|
||||||
template={{
|
summary_fields: {
|
||||||
id: 1,
|
user_capabilities: {
|
||||||
name: 'Template 1',
|
edit: false,
|
||||||
summary_fields: {
|
|
||||||
user_capabilities: {
|
|
||||||
edit: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
context: {
|
|
||||||
router: {
|
|
||||||
history,
|
|
||||||
route: {
|
|
||||||
location: history.location,
|
|
||||||
match: {
|
|
||||||
params: { id: 1 },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}}
|
||||||
}
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
);
|
);
|
||||||
wrapper.find('Link').simulate('click', { button: 0 });
|
wrapper.find('Link').simulate('click', { button: 0 });
|
||||||
expect(history.location.pathname).toEqual(
|
expect(history.location.pathname).toEqual(
|
||||||
'/templates/job_template/1/details'
|
'/templates/job_template/1/details'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('clicking on template from project templates list navigates properly', () => {
|
|
||||||
const history = createMemoryHistory({
|
|
||||||
initialEntries: ['/projects/1/job_templates'],
|
|
||||||
});
|
|
||||||
const wrapper = mountWithContexts(
|
|
||||||
<Route
|
|
||||||
path="/projects/1/job_templates"
|
|
||||||
component={() => (
|
|
||||||
<TemplateListItem
|
|
||||||
isSelected={false}
|
|
||||||
detailUrl=""
|
|
||||||
template={{
|
|
||||||
id: 2,
|
|
||||||
name: 'Template 2',
|
|
||||||
summary_fields: {
|
|
||||||
user_capabilities: {
|
|
||||||
edit: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
context: {
|
|
||||||
router: {
|
|
||||||
history,
|
|
||||||
route: {
|
|
||||||
location: history.location,
|
|
||||||
match: {
|
|
||||||
params: { id: 1 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
wrapper.find('Link').simulate('click', { button: 0 });
|
|
||||||
expect(history.location.pathname).toEqual(
|
|
||||||
'/templates/job_template/2/details'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user