mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
Update status icons (#11561)
* update StatusLabels on job detail * change StatusIcon to use PF circle icons * change status icon to status label on host event modal * update status label on wf job output * update tests for status label changes * fix default status icon color
This commit is contained in:
@@ -1,136 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { string } from 'prop-types';
|
import { string } from 'prop-types';
|
||||||
import styled, { keyframes } from 'styled-components';
|
import icons from './icons';
|
||||||
|
|
||||||
const Pulse = keyframes`
|
const green = '--pf-global--success-color--100';
|
||||||
from {
|
const red = '--pf-global--danger-color--100';
|
||||||
-webkit-transform:scale(1);
|
const blue = '--pf-global--primary-color--100';
|
||||||
}
|
const orange = '--pf-global--palette--orange-300';
|
||||||
to {
|
const gray = '--pf-global--Color--300';
|
||||||
-webkit-transform:scale(0);
|
const colors = {
|
||||||
}
|
success: green,
|
||||||
`;
|
successful: green,
|
||||||
|
healthy: green,
|
||||||
|
ok: green,
|
||||||
|
failed: red,
|
||||||
|
error: red,
|
||||||
|
unreachable: red,
|
||||||
|
running: blue,
|
||||||
|
pending: blue,
|
||||||
|
skipped: blue,
|
||||||
|
waiting: gray,
|
||||||
|
disabled: gray,
|
||||||
|
canceled: orange,
|
||||||
|
changed: orange,
|
||||||
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
function StatusIcon({ status, ...props }) {
|
||||||
align-items: center;
|
const color = colors[status] || '--pf-chart-global--Fill--Color--500';
|
||||||
display: flex;
|
const Icon = icons[status];
|
||||||
flex-flow: column nowrap;
|
return (
|
||||||
height: 14px;
|
<div {...props} data-job-status={status} aria-label={status}>
|
||||||
margin: 5px 0;
|
{Icon ? (
|
||||||
width: 14px;
|
<div style={{ color: `var(${color})` }}>
|
||||||
`;
|
<Icon label={status} />
|
||||||
|
</div>
|
||||||
const WhiteTop = styled.div`
|
) : null}
|
||||||
border: 1px solid #b7b7b7;
|
<span className="pf-screen-reader"> {status} </span>
|
||||||
border-bottom: 0;
|
</div>
|
||||||
background: #ffffff;
|
);
|
||||||
`;
|
}
|
||||||
|
|
||||||
const WhiteBottom = styled.div`
|
|
||||||
border: 1px solid #b7b7b7;
|
|
||||||
border-top: 0;
|
|
||||||
background: #ffffff;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RunningJob = styled(Wrapper)`
|
|
||||||
background-color: #5cb85c;
|
|
||||||
padding-right: 0px;
|
|
||||||
text-shadow: -1px -1px 0 #ffffff, 1px -1px 0 #ffffff, -1px 1px 0 #ffffff,
|
|
||||||
1px 1px 0 #ffffff;
|
|
||||||
animation: ${Pulse} 1.5s linear infinite alternate;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const WaitingJob = styled(Wrapper)`
|
|
||||||
border: 1px solid #d7d7d7;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FinishedJob = styled(Wrapper)`
|
|
||||||
flex: 0 1 auto;
|
|
||||||
> * {
|
|
||||||
width: 14px;
|
|
||||||
height: 7px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SuccessfulTop = styled.div`
|
|
||||||
background-color: #5cb85c;
|
|
||||||
`;
|
|
||||||
const SuccessfulBottom = styled(WhiteBottom)``;
|
|
||||||
|
|
||||||
const FailedTop = styled(WhiteTop)``;
|
|
||||||
const FailedBottom = styled.div`
|
|
||||||
background-color: #d9534f;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const UnreachableTop = styled(WhiteTop)``;
|
|
||||||
const UnreachableBottom = styled.div`
|
|
||||||
background-color: #ff0000;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ChangedTop = styled(WhiteTop)``;
|
|
||||||
const ChangedBottom = styled.div`
|
|
||||||
background-color: #ff9900;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SkippedTop = styled(WhiteTop)``;
|
|
||||||
const SkippedBottom = styled.div`
|
|
||||||
background-color: #2dbaba;
|
|
||||||
`;
|
|
||||||
|
|
||||||
RunningJob.displayName = 'RunningJob';
|
|
||||||
WaitingJob.displayName = 'WaitingJob';
|
|
||||||
FinishedJob.displayName = 'FinishedJob';
|
|
||||||
SuccessfulTop.displayName = 'SuccessfulTop';
|
|
||||||
SuccessfulBottom.displayName = 'SuccessfulBottom';
|
|
||||||
FailedTop.displayName = 'FailedTop';
|
|
||||||
FailedBottom.displayName = 'FailedBottom';
|
|
||||||
UnreachableTop.displayName = 'UnreachableTop';
|
|
||||||
UnreachableBottom.displayName = 'UnreachableBottom';
|
|
||||||
ChangedTop.displayName = 'ChangedTop';
|
|
||||||
ChangedBottom.displayName = 'ChangedBottom';
|
|
||||||
SkippedTop.displayName = 'SkippedTop';
|
|
||||||
SkippedBottom.displayName = 'SkippedBottom';
|
|
||||||
|
|
||||||
const StatusIcon = ({ status, ...props }) => (
|
|
||||||
<div {...props} data-job-status={status} aria-label={status}>
|
|
||||||
{status === 'running' && <RunningJob aria-hidden="true" />}
|
|
||||||
{(status === 'new' ||
|
|
||||||
status === 'pending' ||
|
|
||||||
status === 'waiting' ||
|
|
||||||
status === 'never updated') && <WaitingJob aria-hidden="true" />}
|
|
||||||
{(status === 'failed' || status === 'error' || status === 'canceled') && (
|
|
||||||
<FinishedJob aria-hidden="true">
|
|
||||||
<FailedTop />
|
|
||||||
<FailedBottom />
|
|
||||||
</FinishedJob>
|
|
||||||
)}
|
|
||||||
{(status === 'successful' || status === 'ok') && (
|
|
||||||
<FinishedJob aria-hidden="true">
|
|
||||||
<SuccessfulTop />
|
|
||||||
<SuccessfulBottom />
|
|
||||||
</FinishedJob>
|
|
||||||
)}
|
|
||||||
{status === 'changed' && (
|
|
||||||
<FinishedJob aria-hidden="true">
|
|
||||||
<ChangedTop />
|
|
||||||
<ChangedBottom />
|
|
||||||
</FinishedJob>
|
|
||||||
)}
|
|
||||||
{status === 'skipped' && (
|
|
||||||
<FinishedJob aria-hidden="true">
|
|
||||||
<SkippedTop />
|
|
||||||
<SkippedBottom />
|
|
||||||
</FinishedJob>
|
|
||||||
)}
|
|
||||||
{status === 'unreachable' && (
|
|
||||||
<FinishedJob aria-hidden="true">
|
|
||||||
<UnreachableTop />
|
|
||||||
<UnreachableBottom />
|
|
||||||
</FinishedJob>
|
|
||||||
)}
|
|
||||||
<span className="pf-screen-reader"> {status} </span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
StatusIcon.propTypes = {
|
StatusIcon.propTypes = {
|
||||||
status: string.isRequired,
|
status: string.isRequired,
|
||||||
|
|||||||
@@ -6,53 +6,54 @@ describe('StatusIcon', () => {
|
|||||||
test('renders the successful status', () => {
|
test('renders the successful status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="successful" />);
|
const wrapper = mount(<StatusIcon status="successful" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SuccessfulTop')).toHaveLength(1);
|
expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders running status', () => {
|
test('renders running status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="running" />);
|
const wrapper = mount(<StatusIcon status="running" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon RunningJob')).toHaveLength(1);
|
expect(wrapper.find('RunningIcon')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders waiting status', () => {
|
test('renders waiting status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="waiting" />);
|
const wrapper = mount(<StatusIcon status="waiting" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon WaitingJob')).toHaveLength(1);
|
expect(wrapper.find('ClockIcon')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders failed status', () => {
|
test('renders failed status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="failed" />);
|
const wrapper = mount(<StatusIcon status="failed" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon FailedTop')).toHaveLength(1);
|
expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon FailedBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders a successful status when host status is "ok"', () => {
|
test('renders a successful status when host status is "ok"', () => {
|
||||||
const wrapper = mount(<StatusIcon status="ok" />);
|
const wrapper = mount(<StatusIcon status="ok" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SuccessfulTop')).toHaveLength(1);
|
expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders "failed" host status', () => {
|
test('renders "failed" host status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="failed" />);
|
const wrapper = mount(<StatusIcon status="failed" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon FailedTop')).toHaveLength(1);
|
expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon FailedBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders "changed" host status', () => {
|
test('renders "changed" host status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="changed" />);
|
const wrapper = mount(<StatusIcon status="changed" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon ChangedTop')).toHaveLength(1);
|
expect(wrapper.find('ExclamationTriangleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon ChangedBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders "skipped" host status', () => {
|
test('renders "skipped" host status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="skipped" />);
|
const wrapper = mount(<StatusIcon status="skipped" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SkippedTop')).toHaveLength(1);
|
expect(wrapper.find('MinusCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon SkippedBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders "unreachable" host status', () => {
|
test('renders "unreachable" host status', () => {
|
||||||
const wrapper = mount(<StatusIcon status="unreachable" />);
|
const wrapper = mount(<StatusIcon status="unreachable" />);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon UnreachableTop')).toHaveLength(1);
|
expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon UnreachableBottom')).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
41
awx/ui/src/components/StatusIcon/icons.js
Normal file
41
awx/ui/src/components/StatusIcon/icons.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import styled, { keyframes } from 'styled-components';
|
||||||
|
import {
|
||||||
|
CheckCircleIcon,
|
||||||
|
ExclamationCircleIcon,
|
||||||
|
SyncAltIcon,
|
||||||
|
ExclamationTriangleIcon,
|
||||||
|
ClockIcon,
|
||||||
|
MinusCircleIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
const Spin = keyframes`
|
||||||
|
from {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(1turn);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RunningIcon = styled(SyncAltIcon)`
|
||||||
|
animation: ${Spin} 1.75s linear infinite;
|
||||||
|
`;
|
||||||
|
RunningIcon.displayName = 'RunningIcon';
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
success: CheckCircleIcon,
|
||||||
|
healthy: CheckCircleIcon,
|
||||||
|
successful: CheckCircleIcon,
|
||||||
|
ok: CheckCircleIcon,
|
||||||
|
failed: ExclamationCircleIcon,
|
||||||
|
error: ExclamationCircleIcon,
|
||||||
|
unreachable: ExclamationCircleIcon,
|
||||||
|
running: RunningIcon,
|
||||||
|
pending: ClockIcon,
|
||||||
|
waiting: ClockIcon,
|
||||||
|
disabled: MinusCircleIcon,
|
||||||
|
skipped: MinusCircleIcon,
|
||||||
|
canceled: ExclamationTriangleIcon,
|
||||||
|
changed: ExclamationTriangleIcon,
|
||||||
|
};
|
||||||
|
export default icons;
|
||||||
@@ -3,52 +3,23 @@ import React from 'react';
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { oneOf } from 'prop-types';
|
import { oneOf } from 'prop-types';
|
||||||
import { Label, Tooltip } from '@patternfly/react-core';
|
import { Label, Tooltip } from '@patternfly/react-core';
|
||||||
import {
|
import icons from '../StatusIcon/icons';
|
||||||
CheckCircleIcon,
|
|
||||||
ExclamationCircleIcon,
|
|
||||||
SyncAltIcon,
|
|
||||||
ExclamationTriangleIcon,
|
|
||||||
ClockIcon,
|
|
||||||
MinusCircleIcon,
|
|
||||||
} from '@patternfly/react-icons';
|
|
||||||
import styled, { keyframes } from 'styled-components';
|
|
||||||
|
|
||||||
const Spin = keyframes`
|
|
||||||
from {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(1turn);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RunningIcon = styled(SyncAltIcon)`
|
|
||||||
animation: ${Spin} 1.75s linear infinite;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const colors = {
|
const colors = {
|
||||||
success: 'green',
|
success: 'green',
|
||||||
successful: 'green',
|
successful: 'green',
|
||||||
|
ok: 'green',
|
||||||
healthy: 'green',
|
healthy: 'green',
|
||||||
failed: 'red',
|
failed: 'red',
|
||||||
error: 'red',
|
error: 'red',
|
||||||
|
unreachable: 'red',
|
||||||
running: 'blue',
|
running: 'blue',
|
||||||
pending: 'blue',
|
pending: 'blue',
|
||||||
|
skipped: 'blue',
|
||||||
waiting: 'grey',
|
waiting: 'grey',
|
||||||
disabled: 'grey',
|
disabled: 'grey',
|
||||||
canceled: 'orange',
|
canceled: 'orange',
|
||||||
};
|
changed: 'orange',
|
||||||
const icons = {
|
|
||||||
success: CheckCircleIcon,
|
|
||||||
healthy: CheckCircleIcon,
|
|
||||||
successful: CheckCircleIcon,
|
|
||||||
failed: ExclamationCircleIcon,
|
|
||||||
error: ExclamationCircleIcon,
|
|
||||||
running: RunningIcon,
|
|
||||||
pending: ClockIcon,
|
|
||||||
waiting: ClockIcon,
|
|
||||||
disabled: MinusCircleIcon,
|
|
||||||
canceled: ExclamationTriangleIcon,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function StatusLabel({ status, tooltipContent = '' }) {
|
export default function StatusLabel({ status, tooltipContent = '' }) {
|
||||||
@@ -56,15 +27,19 @@ export default function StatusLabel({ status, tooltipContent = '' }) {
|
|||||||
success: t`Success`,
|
success: t`Success`,
|
||||||
healthy: t`Healthy`,
|
healthy: t`Healthy`,
|
||||||
successful: t`Successful`,
|
successful: t`Successful`,
|
||||||
|
ok: t`OK`,
|
||||||
failed: t`Failed`,
|
failed: t`Failed`,
|
||||||
error: t`Error`,
|
error: t`Error`,
|
||||||
|
unreachable: t`Unreachable`,
|
||||||
running: t`Running`,
|
running: t`Running`,
|
||||||
pending: t`Pending`,
|
pending: t`Pending`,
|
||||||
|
skipped: t`Skipped'`,
|
||||||
waiting: t`Waiting`,
|
waiting: t`Waiting`,
|
||||||
disabled: t`Disabled`,
|
disabled: t`Disabled`,
|
||||||
canceled: t`Canceled`,
|
canceled: t`Canceled`,
|
||||||
|
changed: t`Changed`,
|
||||||
};
|
};
|
||||||
const label = upperCaseStatus[status] || t`Undefined`;
|
const label = upperCaseStatus[status] || status;
|
||||||
const color = colors[status] || 'grey';
|
const color = colors[status] || 'grey';
|
||||||
const Icon = icons[status];
|
const Icon = icons[status];
|
||||||
|
|
||||||
@@ -91,13 +66,17 @@ StatusLabel.propTypes = {
|
|||||||
status: oneOf([
|
status: oneOf([
|
||||||
'success',
|
'success',
|
||||||
'successful',
|
'successful',
|
||||||
|
'ok',
|
||||||
'healthy',
|
'healthy',
|
||||||
'failed',
|
'failed',
|
||||||
'error',
|
'error',
|
||||||
|
'unreachable',
|
||||||
'running',
|
'running',
|
||||||
'pending',
|
'pending',
|
||||||
|
'skipped',
|
||||||
'waiting',
|
'waiting',
|
||||||
'disabled',
|
'disabled',
|
||||||
'canceled',
|
'canceled',
|
||||||
|
'changed',
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ import { VariablesDetail } from 'components/CodeEditor';
|
|||||||
import DeleteButton from 'components/DeleteButton';
|
import DeleteButton from 'components/DeleteButton';
|
||||||
import ErrorDetail from 'components/ErrorDetail';
|
import ErrorDetail from 'components/ErrorDetail';
|
||||||
import { LaunchButton, ReLaunchDropDown } from 'components/LaunchButton';
|
import { LaunchButton, ReLaunchDropDown } from 'components/LaunchButton';
|
||||||
import StatusIcon from 'components/StatusIcon';
|
import StatusLabel from 'components/StatusLabel';
|
||||||
import JobCancelButton from 'components/JobCancelButton';
|
import JobCancelButton from 'components/JobCancelButton';
|
||||||
import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
|
import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
|
||||||
import { getJobModel, isJobRunning } from 'util/jobs';
|
import { getJobModel, isJobRunning } from 'util/jobs';
|
||||||
import { toTitleCase } from 'util/strings';
|
|
||||||
import { formatDateString } from 'util/dates';
|
import { formatDateString } from 'util/dates';
|
||||||
import { Job } from 'types';
|
import { Job } from 'types';
|
||||||
|
|
||||||
@@ -92,25 +91,6 @@ function JobDetail({ job, inventorySourceLabels }) {
|
|||||||
<Link to={`/instance_groups/container_group/${item.id}`}>{item.name}</Link>
|
<Link to={`/instance_groups/container_group/${item.id}`}>{item.name}</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
const buildProjectDetailValue = () => {
|
|
||||||
if (projectUpdate) {
|
|
||||||
return (
|
|
||||||
<StatusDetailValue>
|
|
||||||
<Link to={`/jobs/project/${projectUpdate.id}`}>
|
|
||||||
<StatusIcon status={project.status} />
|
|
||||||
</Link>
|
|
||||||
<Link to={`/projects/${project.id}`}>{project.name}</Link>
|
|
||||||
</StatusDetailValue>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<StatusDetailValue>
|
|
||||||
<StatusIcon status={project.status} />
|
|
||||||
<Link to={`/projects/${project.id}`}>{project.name}</Link>
|
|
||||||
</StatusDetailValue>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
@@ -121,10 +101,7 @@ function JobDetail({ job, inventorySourceLabels }) {
|
|||||||
label={t`Status`}
|
label={t`Status`}
|
||||||
value={
|
value={
|
||||||
<StatusDetailValue>
|
<StatusDetailValue>
|
||||||
{job.status && <StatusIcon status={job.status} />}
|
{job.status && <StatusLabel status={job.status} />}
|
||||||
{job.job_explanation
|
|
||||||
? job.job_explanation
|
|
||||||
: toTitleCase(job.status)}
|
|
||||||
</StatusDetailValue>
|
</StatusDetailValue>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -229,7 +206,7 @@ function JobDetail({ job, inventorySourceLabels }) {
|
|||||||
value={
|
value={
|
||||||
<StatusDetailValue>
|
<StatusDetailValue>
|
||||||
{source_project.status && (
|
{source_project.status && (
|
||||||
<StatusIcon status={source_project.status} />
|
<StatusLabel status={source_project.status} />
|
||||||
)}
|
)}
|
||||||
<Link to={`/projects/${source_project.id}`}>
|
<Link to={`/projects/${source_project.id}`}>
|
||||||
{source_project.name}
|
{source_project.name}
|
||||||
@@ -239,11 +216,26 @@ function JobDetail({ job, inventorySourceLabels }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{project && (
|
{project && (
|
||||||
<Detail
|
<>
|
||||||
dataCy="job-project"
|
<Detail
|
||||||
label={t`Project`}
|
dataCy="job-project"
|
||||||
value={buildProjectDetailValue()}
|
label={t`Project`}
|
||||||
/>
|
value={<Link to={`/projects/${project.id}`}>{project.name}</Link>}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
dataCy="job-project-status"
|
||||||
|
label={t`Project Status`}
|
||||||
|
value={
|
||||||
|
projectUpdate ? (
|
||||||
|
<Link to={`/jobs/project/${projectUpdate.id}`}>
|
||||||
|
<StatusLabel status={project.status} />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<StatusLabel status={project.status} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{scmBranch && (
|
{scmBranch && (
|
||||||
<Detail
|
<Detail
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe('<JobDetail />', () => {
|
|||||||
|
|
||||||
// StatusIcon adds visibly hidden accessibility text " successful "
|
// StatusIcon adds visibly hidden accessibility text " successful "
|
||||||
assertDetail('Job ID', '2');
|
assertDetail('Job ID', '2');
|
||||||
assertDetail('Status', ' successful Successful');
|
assertDetail('Status', 'Successful');
|
||||||
assertDetail('Started', '8/8/2019, 7:24:18 PM');
|
assertDetail('Started', '8/8/2019, 7:24:18 PM');
|
||||||
assertDetail('Finished', '8/8/2019, 7:24:50 PM');
|
assertDetail('Finished', '8/8/2019, 7:24:50 PM');
|
||||||
assertDetail('Job Template', mockJobData.summary_fields.job_template.name);
|
assertDetail('Job Template', mockJobData.summary_fields.job_template.name);
|
||||||
@@ -54,10 +54,7 @@ describe('<JobDetail />', () => {
|
|||||||
assertDetail('Job Type', 'Playbook Run');
|
assertDetail('Job Type', 'Playbook Run');
|
||||||
assertDetail('Launched By', mockJobData.summary_fields.created_by.username);
|
assertDetail('Launched By', mockJobData.summary_fields.created_by.username);
|
||||||
assertDetail('Inventory', mockJobData.summary_fields.inventory.name);
|
assertDetail('Inventory', mockJobData.summary_fields.inventory.name);
|
||||||
assertDetail(
|
assertDetail('Project', mockJobData.summary_fields.project.name);
|
||||||
'Project',
|
|
||||||
` successful ${mockJobData.summary_fields.project.name}`
|
|
||||||
);
|
|
||||||
assertDetail('Revision', mockJobData.scm_revision);
|
assertDetail('Revision', mockJobData.scm_revision);
|
||||||
assertDetail('Playbook', mockJobData.playbook);
|
assertDetail('Playbook', mockJobData.playbook);
|
||||||
assertDetail('Verbosity', '0 (Normal)');
|
assertDetail('Verbosity', '0 (Normal)');
|
||||||
@@ -98,16 +95,13 @@ describe('<JobDetail />', () => {
|
|||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
|
||||||
const statusDetail = wrapper.find('Detail[label="Status"]');
|
const statusDetail = wrapper.find('Detail[label="Status"]');
|
||||||
expect(statusDetail.find('StatusIcon SuccessfulTop')).toHaveLength(1);
|
const statusLabel = statusDetail.find('StatusLabel');
|
||||||
expect(statusDetail.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
|
expect(statusLabel.prop('status')).toEqual('successful');
|
||||||
|
|
||||||
const projectStatusDetail = wrapper.find('Detail[label="Project"]');
|
const projectStatusDetail = wrapper.find('Detail[label="Project Status"]');
|
||||||
expect(projectStatusDetail.find('StatusIcon SuccessfulTop')).toHaveLength(
|
expect(projectStatusDetail.find('StatusLabel')).toHaveLength(1);
|
||||||
1
|
const projectStatusLabel = statusDetail.find('StatusLabel');
|
||||||
);
|
expect(projectStatusLabel.prop('status')).toEqual('successful');
|
||||||
expect(
|
|
||||||
projectStatusDetail.find('StatusIcon SuccessfulBottom')
|
|
||||||
).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not display finished date', () => {
|
test('should not display finished date', () => {
|
||||||
@@ -537,7 +531,7 @@ describe('<JobDetail />', () => {
|
|||||||
webhook_guid: '',
|
webhook_guid: '',
|
||||||
};
|
};
|
||||||
wrapper = mountWithContexts(<JobDetail job={workFlowJob} />);
|
wrapper = mountWithContexts(<JobDetail job={workFlowJob} />);
|
||||||
assertDetail('Status', ' successful Successful');
|
assertDetail('Status', 'Successful');
|
||||||
assertDetail('Started', '7/6/2021, 7:40:17 PM');
|
assertDetail('Started', '7/6/2021, 7:40:17 PM');
|
||||||
assertDetail('Finished', '7/6/2021, 7:40:42 PM');
|
assertDetail('Finished', '7/6/2021, 7:40:42 PM');
|
||||||
assertDetail('Job Template', 'Sliced Job Template');
|
assertDetail('Job Template', 'Sliced Job Template');
|
||||||
|
|||||||
@@ -3,20 +3,12 @@ import { Modal, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { encode } from 'html-entities';
|
import { encode } from 'html-entities';
|
||||||
import StatusIcon from '../../../components/StatusIcon';
|
import StatusLabel from '../../../components/StatusLabel';
|
||||||
import { DetailList, Detail } from '../../../components/DetailList';
|
import { DetailList, Detail } from '../../../components/DetailList';
|
||||||
import ContentEmpty from '../../../components/ContentEmpty';
|
import ContentEmpty from '../../../components/ContentEmpty';
|
||||||
import CodeEditor from '../../../components/CodeEditor';
|
import CodeEditor from '../../../components/CodeEditor';
|
||||||
|
|
||||||
const HostNameDetailValue = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: inline-grid;
|
|
||||||
grid-gap: 10px;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const processEventStatus = (event) => {
|
const processEventStatus = (event) => {
|
||||||
let status = null;
|
let status = null;
|
||||||
if (event.event === 'runner_on_unreachable') {
|
if (event.event === 'runner_on_unreachable') {
|
||||||
@@ -117,15 +109,13 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false }) {
|
|||||||
style={{ alignItems: 'center', marginTop: '20px' }}
|
style={{ alignItems: 'center', marginTop: '20px' }}
|
||||||
gutter="sm"
|
gutter="sm"
|
||||||
>
|
>
|
||||||
<Detail
|
<Detail label={t`Host`} value={hostEvent.host_name} />
|
||||||
label={t`Host Name`}
|
{hostStatus ? (
|
||||||
value={
|
<Detail
|
||||||
<HostNameDetailValue>
|
label={t`Status`}
|
||||||
{hostStatus ? <StatusIcon status={hostStatus} /> : null}
|
value={<StatusLabel status={hostStatus} />}
|
||||||
{hostEvent.host_name}
|
/>
|
||||||
</HostNameDetailValue>
|
) : null}
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Detail label={t`Play`} value={hostEvent.play} />
|
<Detail label={t`Play`} value={hostEvent.play} />
|
||||||
<Detail label={t`Task`} value={hostEvent.task} />
|
<Detail label={t`Task`} value={hostEvent.task} />
|
||||||
<Detail
|
<Detail
|
||||||
|
|||||||
@@ -63,18 +63,6 @@ const jsonValue = `{
|
|||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
// let detailsSection;
|
|
||||||
// let jsonSection;
|
|
||||||
// let standardOutSection;
|
|
||||||
// let standardErrorSection;
|
|
||||||
//
|
|
||||||
// const findSections = wrapper => {
|
|
||||||
// detailsSection = wrapper.find('section').at(0);
|
|
||||||
// jsonSection = wrapper.find('section').at(1);
|
|
||||||
// standardOutSection = wrapper.find('section').at(2);
|
|
||||||
// standardErrorSection = wrapper.find('section').at(3);
|
|
||||||
// };
|
|
||||||
|
|
||||||
describe('HostEventModal', () => {
|
describe('HostEventModal', () => {
|
||||||
test('initially renders successfully', () => {
|
test('initially renders successfully', () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
@@ -105,14 +93,14 @@ describe('HostEventModal', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const detail = wrapper.find('Detail').first();
|
const detail = wrapper.find('Detail').first();
|
||||||
expect(detail.prop('value').props.children).toEqual([null, 'foo']);
|
expect(detail.prop('value')).toEqual('foo');
|
||||||
assertDetail(1, 'Play', 'all');
|
assertDetail(1, 'Play', 'all');
|
||||||
assertDetail(2, 'Task', 'command');
|
assertDetail(2, 'Task', 'command');
|
||||||
assertDetail(3, 'Module', 'command');
|
assertDetail(3, 'Module', 'command');
|
||||||
assertDetail(4, 'Command', hostEvent.event_data.res.cmd);
|
assertDetail(4, 'Command', hostEvent.event_data.res.cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display successful host status icon', () => {
|
test('should display successful host status label', () => {
|
||||||
const successfulHostEvent = { ...hostEvent, changed: false };
|
const successfulHostEvent = { ...hostEvent, changed: false };
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<HostEventModal
|
<HostEventModal
|
||||||
@@ -121,25 +109,21 @@ describe('HostEventModal', () => {
|
|||||||
isOpen
|
isOpen
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const icon = wrapper.find('StatusIcon');
|
const icon = wrapper.find('StatusLabel');
|
||||||
expect(icon.prop('status')).toBe('ok');
|
expect(icon.prop('status')).toBe('ok');
|
||||||
expect(icon.find('StatusIcon SuccessfulTop').length).toBe(1);
|
|
||||||
expect(icon.find('StatusIcon SuccessfulBottom').length).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display skipped host status icon', () => {
|
test('should display skipped host status label', () => {
|
||||||
const skippedHostEvent = { ...hostEvent, event: 'runner_on_skipped' };
|
const skippedHostEvent = { ...hostEvent, event: 'runner_on_skipped' };
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<HostEventModal hostEvent={skippedHostEvent} onClose={() => {}} isOpen />
|
<HostEventModal hostEvent={skippedHostEvent} onClose={() => {}} isOpen />
|
||||||
);
|
);
|
||||||
|
|
||||||
const icon = wrapper.find('StatusIcon');
|
const icon = wrapper.find('StatusLabel');
|
||||||
expect(icon.prop('status')).toBe('skipped');
|
expect(icon.prop('status')).toBe('skipped');
|
||||||
expect(icon.find('StatusIcon SkippedTop').length).toBe(1);
|
|
||||||
expect(icon.find('StatusIcon SkippedBottom').length).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display unreachable host status icon', () => {
|
test('should display unreachable host status label', () => {
|
||||||
const unreachableHostEvent = {
|
const unreachableHostEvent = {
|
||||||
...hostEvent,
|
...hostEvent,
|
||||||
event: 'runner_on_unreachable',
|
event: 'runner_on_unreachable',
|
||||||
@@ -153,13 +137,11 @@ describe('HostEventModal', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const icon = wrapper.find('StatusIcon');
|
const icon = wrapper.find('StatusLabel');
|
||||||
expect(icon.prop('status')).toBe('unreachable');
|
expect(icon.prop('status')).toBe('unreachable');
|
||||||
expect(icon.find('StatusIcon UnreachableTop').length).toBe(1);
|
|
||||||
expect(icon.find('StatusIcon UnreachableBottom').length).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display failed host status icon', () => {
|
test('should display failed host status label', () => {
|
||||||
const unreachableHostEvent = {
|
const unreachableHostEvent = {
|
||||||
...hostEvent,
|
...hostEvent,
|
||||||
changed: false,
|
changed: false,
|
||||||
@@ -174,10 +156,8 @@ describe('HostEventModal', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const icon = wrapper.find('StatusIcon');
|
const icon = wrapper.find('StatusLabel');
|
||||||
expect(icon.prop('status')).toBe('failed');
|
expect(icon.prop('status')).toBe('failed');
|
||||||
expect(icon.find('StatusIcon FailedTop').length).toBe(1);
|
|
||||||
expect(icon.find('StatusIcon FailedBottom').length).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display JSON tab content on tab click', () => {
|
test('should display JSON tab content on tab click', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { CardBody as _CardBody } from 'components/Card';
|
|||||||
import ContentError from 'components/ContentError';
|
import ContentError from 'components/ContentError';
|
||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
import ErrorDetail from 'components/ErrorDetail';
|
import ErrorDetail from 'components/ErrorDetail';
|
||||||
import StatusIcon from 'components/StatusIcon';
|
import StatusLabel from 'components/StatusLabel';
|
||||||
import { JobEventsAPI } from 'api';
|
import { JobEventsAPI } from 'api';
|
||||||
|
|
||||||
import { getJobModel, isJobRunning } from 'util/jobs';
|
import { getJobModel, isJobRunning } from 'util/jobs';
|
||||||
@@ -51,7 +51,7 @@ const HeaderTitle = styled.div`
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
h1 {
|
h1 {
|
||||||
margin-left: 10px;
|
margin-right: 10px;
|
||||||
font-weight: var(--pf-global--FontWeight--bold);
|
font-weight: var(--pf-global--FontWeight--bold);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -669,8 +669,8 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
)}
|
)}
|
||||||
<OutputHeader>
|
<OutputHeader>
|
||||||
<HeaderTitle>
|
<HeaderTitle>
|
||||||
<StatusIcon status={job.status} />
|
|
||||||
<h1>{job.name}</h1>
|
<h1>{job.name}</h1>
|
||||||
|
<StatusLabel status={job.status} />
|
||||||
</HeaderTitle>
|
</HeaderTitle>
|
||||||
<OutputToolbar
|
<OutputToolbar
|
||||||
job={job}
|
job={job}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
ProjectDiagramIcon,
|
ProjectDiagramIcon,
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import StatusIcon from 'components/StatusIcon';
|
import StatusLabel from 'components/StatusLabel';
|
||||||
import {
|
import {
|
||||||
WorkflowDispatchContext,
|
WorkflowDispatchContext,
|
||||||
WorkflowStateContext,
|
WorkflowStateContext,
|
||||||
@@ -24,8 +24,13 @@ const Toolbar = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ToolbarJob = styled.div`
|
const ToolbarJob = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
|
||||||
|
h1 {
|
||||||
|
margin-right: 10px;
|
||||||
|
font-weight: var(--pf-global--FontWeight--bold);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ToolbarActions = styled.div`
|
const ToolbarActions = styled.div`
|
||||||
@@ -57,10 +62,6 @@ const ActionButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StatusIconWithMargin = styled(StatusIcon)`
|
|
||||||
margin-right: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
function WorkflowOutputToolbar({ job }) {
|
function WorkflowOutputToolbar({ job }) {
|
||||||
const dispatch = useContext(WorkflowDispatchContext);
|
const dispatch = useContext(WorkflowDispatchContext);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -75,8 +76,8 @@ function WorkflowOutputToolbar({ job }) {
|
|||||||
return (
|
return (
|
||||||
<Toolbar id="workflow-output-toolbar" ouiaId="workflow-output-toolbar">
|
<Toolbar id="workflow-output-toolbar" ouiaId="workflow-output-toolbar">
|
||||||
<ToolbarJob>
|
<ToolbarJob>
|
||||||
<StatusIconWithMargin status={job.status} />
|
<h1>{job.name}</h1>
|
||||||
<b>{job.name}</b>
|
<StatusLabel status={job.status} />
|
||||||
</ToolbarJob>
|
</ToolbarJob>
|
||||||
<ToolbarActions>
|
<ToolbarActions>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
Reference in New Issue
Block a user