mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 18:51:06 -03:30
240 jt details skeleton v2 (#273)
* adding package-lock.json * deleted unsured file * Adds a Bottom Border Component * Updates dependencies * Adds JT Details and tests for it * merge and rebase * addresses UI PR issues * Addresses PR Issues and fixes failing tests. * Updates to code, fixes package and package-lock.json addresses PR Issues * fixes package files
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import Base from '../Base';
|
import Base from '../Base';
|
||||||
|
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
||||||
|
|
||||||
class JobTemplates extends Base {
|
class JobTemplates extends InstanceGroupsMixin(Base) {
|
||||||
constructor (http) {
|
constructor (http) {
|
||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/job_templates/';
|
this.baseUrl = '/api/v2/job_templates/';
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const DetailName = styled(({ fullWidth, ...props }) => (
|
|||||||
<TextListItem {...props} />
|
<TextListItem {...props} />
|
||||||
))`
|
))`
|
||||||
font-weight: var(--pf-global--FontWeight--bold);
|
font-weight: var(--pf-global--FontWeight--bold);
|
||||||
text-align: right;
|
|
||||||
${props => props.fullWidth && `
|
${props => props.fullWidth && `
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
`}
|
`}
|
||||||
|
|||||||
@@ -4,17 +4,15 @@ import { t } from '@lingui/macro';
|
|||||||
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
|
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
|
||||||
import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core';
|
import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { OrganizationsAPI } from '@api';
|
|
||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import ContentError from '@components/ContentError';
|
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
|
import ContentError from '@components/ContentError';
|
||||||
import { OrganizationAccess } from './OrganizationAccess';
|
import { OrganizationAccess } from './OrganizationAccess';
|
||||||
import OrganizationDetail from './OrganizationDetail';
|
import OrganizationDetail from './OrganizationDetail';
|
||||||
import OrganizationEdit from './OrganizationEdit';
|
import OrganizationEdit from './OrganizationEdit';
|
||||||
import OrganizationNotifications from './OrganizationNotifications';
|
import OrganizationNotifications from './OrganizationNotifications';
|
||||||
import OrganizationTeams from './OrganizationTeams';
|
import OrganizationTeams from './OrganizationTeams';
|
||||||
|
import { OrganizationsAPI } from '@api';
|
||||||
|
|
||||||
class Organization extends Component {
|
class Organization extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -141,10 +139,11 @@ class Organization extends Component {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
let cardHeader = (
|
let cardHeader = (
|
||||||
<CardHeader>
|
<CardHeader style={{ padding: 0 }}>
|
||||||
<RoutedTabs
|
<RoutedTabs
|
||||||
match={match}
|
match={match}
|
||||||
history={history}
|
history={history}
|
||||||
|
labeltext={i18n._(t`Organization detail tabs`)}
|
||||||
tabsArray={tabsArray}
|
tabsArray={tabsArray}
|
||||||
/>
|
/>
|
||||||
<CardCloseButton linkTo="/organizations" />
|
<CardCloseButton linkTo="/organizations" />
|
||||||
|
|||||||
@@ -426,9 +426,9 @@ exports[`<OrganizationAccessItem /> initially renders succesfully 1`] = `
|
|||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "Detail__DetailName-sc-16ypsyv-0",
|
"componentId": "Detail__DetailName-sc-16ypsyv-0",
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"lastClassName": "gRioSK",
|
"lastClassName": "erdIBg",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"font-weight:var(--pf-global--FontWeight--bold);text-align:right;",
|
"font-weight:var(--pf-global--FontWeight--bold);",
|
||||||
[Function],
|
[Function],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -446,16 +446,16 @@ exports[`<OrganizationAccessItem /> initially renders succesfully 1`] = `
|
|||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
component="dt"
|
component="dt"
|
||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
>
|
>
|
||||||
<TextListItem
|
<TextListItem
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
component="dt"
|
component="dt"
|
||||||
>
|
>
|
||||||
<dt
|
<dt
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
data-pf-content={true}
|
data-pf-content={true}
|
||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
@@ -603,9 +603,9 @@ exports[`<OrganizationAccessItem /> initially renders succesfully 1`] = `
|
|||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "Detail__DetailName-sc-16ypsyv-0",
|
"componentId": "Detail__DetailName-sc-16ypsyv-0",
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"lastClassName": "gRioSK",
|
"lastClassName": "erdIBg",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"font-weight:var(--pf-global--FontWeight--bold);text-align:right;",
|
"font-weight:var(--pf-global--FontWeight--bold);",
|
||||||
[Function],
|
[Function],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -623,16 +623,16 @@ exports[`<OrganizationAccessItem /> initially renders succesfully 1`] = `
|
|||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
component="dt"
|
component="dt"
|
||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
>
|
>
|
||||||
<TextListItem
|
<TextListItem
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
component="dt"
|
component="dt"
|
||||||
>
|
>
|
||||||
<dt
|
<dt
|
||||||
className="Detail__DetailName-sc-16ypsyv-0 gRioSK"
|
className="Detail__DetailName-sc-16ypsyv-0 erdIBg"
|
||||||
data-pf-content={true}
|
data-pf-content={true}
|
||||||
>
|
>
|
||||||
Team Roles
|
Team Roles
|
||||||
|
|||||||
340
src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
Normal file
340
src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { CardBody, Button, TextList, TextListItem, TextListItemVariants, TextListVariants } from '@patternfly/react-core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
import { ChipGroup, Chip } from '../../../components/Chip';
|
||||||
|
import { DetailList, Detail } from '../../../components/DetailList';
|
||||||
|
import { JobTemplatesAPI } from '../../../api';
|
||||||
|
import { toTitleCase } from '../../../util/strings';
|
||||||
|
|
||||||
|
const ButtonGroup = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
& > :not(:first-child){
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
class JobTemplateDetail extends Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
contentError: false,
|
||||||
|
hasContentLoading: true,
|
||||||
|
instanceGroups: []
|
||||||
|
};
|
||||||
|
this.readInstanceGroups = this.readInstanceGroups.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.readInstanceGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
async readInstanceGroups () {
|
||||||
|
const { match } = this.props;
|
||||||
|
try {
|
||||||
|
const { data } = await JobTemplatesAPI.readInstanceGroups(match.params.id);
|
||||||
|
this.setState({ instanceGroups: [...data.results] });
|
||||||
|
} catch {
|
||||||
|
this.setState({ contentError: true });
|
||||||
|
} finally {
|
||||||
|
this.setState({ hasContentLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
template: {
|
||||||
|
allow_simultaneous,
|
||||||
|
become_enabled,
|
||||||
|
created,
|
||||||
|
description,
|
||||||
|
diff_mode,
|
||||||
|
forks,
|
||||||
|
host_config_key,
|
||||||
|
job_slice_count,
|
||||||
|
job_tags,
|
||||||
|
job_type,
|
||||||
|
inventory,
|
||||||
|
name,
|
||||||
|
limit,
|
||||||
|
modified,
|
||||||
|
playbook,
|
||||||
|
project,
|
||||||
|
skip_tags,
|
||||||
|
timeout,
|
||||||
|
summary_fields,
|
||||||
|
use_fact_cache,
|
||||||
|
url,
|
||||||
|
verbosity
|
||||||
|
},
|
||||||
|
hasTemplateLoading,
|
||||||
|
i18n,
|
||||||
|
} = this.props;
|
||||||
|
const { instanceGroups, hasContentLoading, contentError } = this.state;
|
||||||
|
const verbosityOptions = [
|
||||||
|
{ verbosity: 0, details: i18n._(t`0 (Normal)`) },
|
||||||
|
{ verbosity: 1, details: i18n._(t`1 (Verbose)`) },
|
||||||
|
{ verbosity: 2, details: i18n._(t`2 (More Verbose)`) },
|
||||||
|
{ verbosity: 3, details: i18n._(t`3 (Debug)`) },
|
||||||
|
{ verbosity: 4, details: i18n._(t`4 (Connection Debug)`) },
|
||||||
|
{ verbosity: 5, details: i18n._(t`5 (WinRM Debug)`) },
|
||||||
|
];
|
||||||
|
const verbosityDetails = verbosityOptions.filter(
|
||||||
|
option => option.verbosity === verbosity
|
||||||
|
);
|
||||||
|
const generateCallBackUrl = `${window.location.origin + url}callback/`;
|
||||||
|
const isInitialized = !hasTemplateLoading && !hasContentLoading;
|
||||||
|
|
||||||
|
const credentialType = (c) => (c === 'aws' || c === 'ssh' ? c.toUpperCase() : toTitleCase(c));
|
||||||
|
|
||||||
|
const renderOptionsField = become_enabled || host_config_key || allow_simultaneous
|
||||||
|
|| use_fact_cache;
|
||||||
|
|
||||||
|
const renderOptions = (
|
||||||
|
<TextList component={TextListVariants.ul}>
|
||||||
|
{become_enabled && (
|
||||||
|
<TextListItem
|
||||||
|
component={TextListItemVariants.li}
|
||||||
|
>
|
||||||
|
{i18n._(t`Enable Privilege Escalation`)}
|
||||||
|
</TextListItem>
|
||||||
|
)}
|
||||||
|
{host_config_key && (
|
||||||
|
<TextListItem
|
||||||
|
component={TextListItemVariants.li}
|
||||||
|
>
|
||||||
|
{i18n._(t`Allow Provisioning Callbacks`)}
|
||||||
|
</TextListItem>
|
||||||
|
)}
|
||||||
|
{allow_simultaneous && (
|
||||||
|
<TextListItem
|
||||||
|
component={TextListItemVariants.li}
|
||||||
|
>
|
||||||
|
{i18n._(t`Enable Concurrent Jobs`)}
|
||||||
|
</TextListItem>
|
||||||
|
)}
|
||||||
|
{use_fact_cache && (
|
||||||
|
<TextListItem
|
||||||
|
component={TextListItemVariants.li}
|
||||||
|
>
|
||||||
|
{i18n._(t`Use Fact Cache`)}
|
||||||
|
</TextListItem>
|
||||||
|
)}
|
||||||
|
</TextList>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (contentError) {
|
||||||
|
return (<ContentError />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasContentLoading) {
|
||||||
|
return (<ContentLoading />);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
isInitialized && (
|
||||||
|
<CardBody css="padding-top: 20px;">
|
||||||
|
<DetailList gutter="sm">
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
value={name}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
value={description}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Job Type`)}
|
||||||
|
value={job_type}
|
||||||
|
/>
|
||||||
|
{inventory && (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Inventory`)}
|
||||||
|
value={summary_fields.inventory.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{project && (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Project`)}
|
||||||
|
value={summary_fields.project.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Playbook`)}
|
||||||
|
value={playbook}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Forks`)}
|
||||||
|
value={forks || '0'}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Limit`)}
|
||||||
|
value={limit}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Verbosity`)}
|
||||||
|
value={verbosityDetails[0].details}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Timeout`)}
|
||||||
|
value={timeout || '0'}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Created`)}
|
||||||
|
value={`${created} by ${summary_fields.created_by.username}`} // TODO: link to user in users
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Last Modified`)}
|
||||||
|
value={`${modified} by ${summary_fields.modified_by.username}`} // TODO: link to user in users
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Show Changes`)}
|
||||||
|
value={diff_mode ? 'On' : 'Off'}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t` Job Slicing`)}
|
||||||
|
value={job_slice_count}
|
||||||
|
/>
|
||||||
|
{host_config_key && (
|
||||||
|
<React.Fragment>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Host Config Key`)}
|
||||||
|
value={host_config_key}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Provisioning Callback URL`)}
|
||||||
|
value={generateCallBackUrl}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
{renderOptionsField && (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Options`)}
|
||||||
|
value={renderOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(summary_fields.credentials && summary_fields.credentials.length > 0) && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Credentials`)}
|
||||||
|
value={(
|
||||||
|
<ChipGroup showOverflowAfter={5}>
|
||||||
|
{summary_fields.credentials.map(c => (
|
||||||
|
<Chip key={c.id} isReadOnly>
|
||||||
|
<strong className="credential">
|
||||||
|
{c.kind ? credentialType(c.kind) : i18n._(t`Cloud`)}
|
||||||
|
:
|
||||||
|
</strong>
|
||||||
|
{` ${c.name}`}
|
||||||
|
</Chip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ChipGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(summary_fields.labels && summary_fields.labels.results.length > 0) && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Labels`)}
|
||||||
|
value={(
|
||||||
|
<ChipGroup showOverflowAfter={5}>
|
||||||
|
{summary_fields.labels.results.map(l => (
|
||||||
|
<Chip key={l.id} isReadOnly>
|
||||||
|
{l.name}
|
||||||
|
</Chip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ChipGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(instanceGroups.length > 0) && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Instance Groups`)}
|
||||||
|
value={(
|
||||||
|
<ChipGroup showOverflowAfter={5}>
|
||||||
|
{instanceGroups.map(ig => (
|
||||||
|
<Chip key={ig.id} isReadOnly>
|
||||||
|
{ig.name}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(job_tags && job_tags.length > 0) && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Job tags`)}
|
||||||
|
value={(
|
||||||
|
<ChipGroup showOverflowAfter={5}>
|
||||||
|
{job_tags.split(',').map(jobTag => (
|
||||||
|
<Chip key={jobTag} isReadOnly>
|
||||||
|
{jobTag}
|
||||||
|
</Chip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ChipGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(skip_tags && skip_tags.length > 0) && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Skip tags`)}
|
||||||
|
value={(
|
||||||
|
<ChipGroup showOverflowAfter={5}>
|
||||||
|
{skip_tags.split(',').map(skipTag => (
|
||||||
|
<Chip key={skipTag} isReadOnly>
|
||||||
|
{skipTag}
|
||||||
|
</Chip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ChipGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DetailList>
|
||||||
|
<ButtonGroup>
|
||||||
|
{summary_fields.user_capabilities.edit && (
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to="/home"
|
||||||
|
aria-label={i18n._(t`Edit`)}
|
||||||
|
|
||||||
|
>
|
||||||
|
{i18n._(t`Edit`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
component={Link}
|
||||||
|
to="/templates"
|
||||||
|
aria-label={i18n._(t`Launch`)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Launch`)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
component={Link}
|
||||||
|
to="/templates"
|
||||||
|
aria-label={i18n._(t`Close`)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Close`)}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</CardBody>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { JobTemplateDetail as _JobTemplateDetail };
|
||||||
|
export default withI18n()(withRouter(JobTemplateDetail));
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mountWithContexts, waitForElement } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import JobTemplateDetail, { _JobTemplateDetail } from './JobTemplateDetail';
|
||||||
|
|
||||||
|
describe('<JobTemplateDetail />', () => {
|
||||||
|
const template = {
|
||||||
|
forks: 1,
|
||||||
|
host_config_key: 'ssh',
|
||||||
|
name: 'Temp 1',
|
||||||
|
job_type: 'run',
|
||||||
|
inventory: 1,
|
||||||
|
limit: '1',
|
||||||
|
project: 7,
|
||||||
|
playbook: '',
|
||||||
|
id: 1,
|
||||||
|
verbosity: 1,
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: { edit: true },
|
||||||
|
created_by: { username: 'Joe' },
|
||||||
|
modified_by: { username: 'Joe' },
|
||||||
|
credentials: [
|
||||||
|
{ id: 1, kind: 'ssh', name: 'Credential 1' },
|
||||||
|
{ id: 2, kind: 'awx', name: 'Credential 2' }
|
||||||
|
],
|
||||||
|
inventory: { name: 'Inventory' },
|
||||||
|
project: { name: 'Project' }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockInstanceGroups = {
|
||||||
|
count: 5,
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{ id: 1, name: 'IG1' },
|
||||||
|
{ id: 2, name: 'IG2' }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const readInstanceGroups = jest.spyOn(_JobTemplateDetail.prototype, 'readInstanceGroups');
|
||||||
|
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<JobTemplateDetail
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('When component mounts API is called to get instance groups', async (done) => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<JobTemplateDetail
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitForElement(wrapper, 'JobTemplateDetail', (el) => el.state('hasContentLoading') === true);
|
||||||
|
expect(readInstanceGroups).toHaveBeenCalled();
|
||||||
|
await waitForElement(wrapper, 'JobTemplateDetail', (el) => el.state('hasContentLoading') === false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
test('Edit button is absent when user does not have edit privilege', async (done) => {
|
||||||
|
const regularUser = {
|
||||||
|
forks: 1,
|
||||||
|
host_config_key: 'ssh',
|
||||||
|
name: 'Temp 1',
|
||||||
|
job_tags: 'cookies,pizza',
|
||||||
|
job_type: 'run',
|
||||||
|
inventory: 1,
|
||||||
|
limit: '1',
|
||||||
|
project: 7,
|
||||||
|
playbook: '',
|
||||||
|
id: 1,
|
||||||
|
verbosity: 0,
|
||||||
|
created_by: 'Alex',
|
||||||
|
skip_tags: 'coffe,tea',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: { edit: false },
|
||||||
|
created_by: { username: 'Joe' },
|
||||||
|
modified_by: { username: 'Joe' },
|
||||||
|
inventory: { name: 'Inventory' },
|
||||||
|
project: { name: 'Project' },
|
||||||
|
labels: { count: 1, results: [{ name: 'Label', id: 1 }] }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<JobTemplateDetail
|
||||||
|
template={regularUser}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const jobTemplateDetail = wrapper.find('JobTemplateDetail');
|
||||||
|
const editButton = jobTemplateDetail.find('button[aria-label="Edit"]');
|
||||||
|
|
||||||
|
jobTemplateDetail.setState({
|
||||||
|
instanceGroups: mockInstanceGroups, hasContentLoading: false, contentError: false
|
||||||
|
});
|
||||||
|
expect(editButton.length).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Credential type is Cloud if credential.kind is null', async (done) => {
|
||||||
|
template.summary_fields.credentials = [{ id: 1, name: 'cred', kind: null, }];
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<JobTemplateDetail
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const jobTemplateDetail = wrapper.find('JobTemplateDetail');
|
||||||
|
jobTemplateDetail.setState({
|
||||||
|
instanceGroups: mockInstanceGroups.data.results, hasContentLoading: false, contentError: false
|
||||||
|
});
|
||||||
|
const cred = wrapper.find('strong.credential');
|
||||||
|
expect(cred.text()).toContain('Cloud:');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<JobTemplateDetail /> initially renders succesfully 1`] = `
|
||||||
|
<Wrap>
|
||||||
|
<WithI18n
|
||||||
|
template={
|
||||||
|
Object {
|
||||||
|
"forks": 1,
|
||||||
|
"host_config_key": "ssh",
|
||||||
|
"id": 1,
|
||||||
|
"inventory": 1,
|
||||||
|
"job_type": "run",
|
||||||
|
"limit": "1",
|
||||||
|
"name": "Temp 1",
|
||||||
|
"playbook": "",
|
||||||
|
"project": 7,
|
||||||
|
"summary_fields": Object {
|
||||||
|
"created_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"credentials": Array [
|
||||||
|
Object {
|
||||||
|
"id": 1,
|
||||||
|
"kind": "ssh",
|
||||||
|
"name": "Credential 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": 2,
|
||||||
|
"kind": "awx",
|
||||||
|
"name": "Credential 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"inventory": Object {
|
||||||
|
"name": "Inventory",
|
||||||
|
},
|
||||||
|
"modified_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"project": Object {
|
||||||
|
"name": "Project",
|
||||||
|
},
|
||||||
|
"user_capabilities": Object {
|
||||||
|
"edit": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"verbosity": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<I18n
|
||||||
|
update={true}
|
||||||
|
withHash={true}
|
||||||
|
>
|
||||||
|
<withRouter(JobTemplateDetail)
|
||||||
|
i18n={"/i18n/"}
|
||||||
|
template={
|
||||||
|
Object {
|
||||||
|
"forks": 1,
|
||||||
|
"host_config_key": "ssh",
|
||||||
|
"id": 1,
|
||||||
|
"inventory": 1,
|
||||||
|
"job_type": "run",
|
||||||
|
"limit": "1",
|
||||||
|
"name": "Temp 1",
|
||||||
|
"playbook": "",
|
||||||
|
"project": 7,
|
||||||
|
"summary_fields": Object {
|
||||||
|
"created_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"credentials": Array [
|
||||||
|
Object {
|
||||||
|
"id": 1,
|
||||||
|
"kind": "ssh",
|
||||||
|
"name": "Credential 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": 2,
|
||||||
|
"kind": "awx",
|
||||||
|
"name": "Credential 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"inventory": Object {
|
||||||
|
"name": "Inventory",
|
||||||
|
},
|
||||||
|
"modified_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"project": Object {
|
||||||
|
"name": "Project",
|
||||||
|
},
|
||||||
|
"user_capabilities": Object {
|
||||||
|
"edit": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"verbosity": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route>
|
||||||
|
<JobTemplateDetail
|
||||||
|
history={"/history/"}
|
||||||
|
i18n={"/i18n/"}
|
||||||
|
location={
|
||||||
|
Object {
|
||||||
|
"hash": "",
|
||||||
|
"pathname": "",
|
||||||
|
"search": "",
|
||||||
|
"state": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match={
|
||||||
|
Object {
|
||||||
|
"isExact": false,
|
||||||
|
"params": Object {},
|
||||||
|
"path": "",
|
||||||
|
"url": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template={
|
||||||
|
Object {
|
||||||
|
"forks": 1,
|
||||||
|
"host_config_key": "ssh",
|
||||||
|
"id": 1,
|
||||||
|
"inventory": 1,
|
||||||
|
"job_type": "run",
|
||||||
|
"limit": "1",
|
||||||
|
"name": "Temp 1",
|
||||||
|
"playbook": "",
|
||||||
|
"project": 7,
|
||||||
|
"summary_fields": Object {
|
||||||
|
"created_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"credentials": Array [
|
||||||
|
Object {
|
||||||
|
"id": 1,
|
||||||
|
"kind": "ssh",
|
||||||
|
"name": "Credential 1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": 2,
|
||||||
|
"kind": "awx",
|
||||||
|
"name": "Credential 2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"inventory": Object {
|
||||||
|
"name": "Inventory",
|
||||||
|
},
|
||||||
|
"modified_by": Object {
|
||||||
|
"username": "Joe",
|
||||||
|
},
|
||||||
|
"project": Object {
|
||||||
|
"name": "Project",
|
||||||
|
},
|
||||||
|
"user_capabilities": Object {
|
||||||
|
"edit": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"verbosity": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<WithI18n>
|
||||||
|
<I18n
|
||||||
|
update={true}
|
||||||
|
withHash={true}
|
||||||
|
>
|
||||||
|
<ContentLoading
|
||||||
|
i18n={"/i18n/"}
|
||||||
|
>
|
||||||
|
<EmptyState
|
||||||
|
className=""
|
||||||
|
variant="large"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pf-c-empty-state pf-m-lg"
|
||||||
|
>
|
||||||
|
<EmptyStateBody
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="pf-c-empty-state__body"
|
||||||
|
>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
</EmptyStateBody>
|
||||||
|
</div>
|
||||||
|
</EmptyState>
|
||||||
|
</ContentLoading>
|
||||||
|
</I18n>
|
||||||
|
</WithI18n>
|
||||||
|
</JobTemplateDetail>
|
||||||
|
</Route>
|
||||||
|
</withRouter(JobTemplateDetail)>
|
||||||
|
</I18n>
|
||||||
|
</WithI18n>
|
||||||
|
</Wrap>
|
||||||
|
`;
|
||||||
4
src/screens/Template/JobTemplateDetail/index.js
Normal file
4
src/screens/Template/JobTemplateDetail/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import JobTemplateDetail from './JobTemplateDetail';
|
||||||
|
|
||||||
|
export { JobTemplateDetail as _JobTemplateDetail };
|
||||||
|
export default JobTemplateDetail;
|
||||||
@@ -7,6 +7,7 @@ import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
|
|||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
|
import JobTemplateDetail from './JobTemplateDetail';
|
||||||
import { JobTemplatesAPI } from '@api';
|
import { JobTemplatesAPI } from '@api';
|
||||||
|
|
||||||
class Template extends Component {
|
class Template extends Component {
|
||||||
@@ -85,7 +86,11 @@ class Template extends Component {
|
|||||||
<Route
|
<Route
|
||||||
path="/templates/:templateType/:id/details"
|
path="/templates/:templateType/:id/details"
|
||||||
render={() => (
|
render={() => (
|
||||||
<span>Coming soon!</span>
|
<JobTemplateDetail
|
||||||
|
match={match}
|
||||||
|
hasTemplateLoading={hasContentLoading}
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
17
src/screens/Template/Template.test.jsx
Normal file
17
src/screens/Template/Template.test.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import Template, { _Template } from './Template';
|
||||||
|
|
||||||
|
describe('<Template />', () => {
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mountWithContexts(<Template />);
|
||||||
|
});
|
||||||
|
test('When component mounts API is called and the response is put in state', async (done) => {
|
||||||
|
const readTemplate = jest.spyOn(_Template.prototype, 'readTemplate');
|
||||||
|
const wrapper = mountWithContexts(<Template />);
|
||||||
|
await waitForElement(wrapper, 'Template', (el) => el.state('hasContentLoading') === true);
|
||||||
|
expect(readTemplate).toHaveBeenCalled();
|
||||||
|
await waitForElement(wrapper, 'Template', (el) => el.state('hasContentLoading') === true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@ export function ucFirst (str) {
|
|||||||
return `${str[0].toUpperCase()}${str.substr(1)}`;
|
return `${str[0].toUpperCase()}${str.substr(1)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toTitleCase = (type) => type
|
export const toTitleCase = (string) => string
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.split('_')
|
.split('_')
|
||||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
|||||||
Reference in New Issue
Block a user