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:
Keith Grant
2022-01-24 11:01:02 -08:00
committed by GitHub
parent af69b25eaa
commit 85cc67fb4e
10 changed files with 169 additions and 284 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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', () => {

View File

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

View File

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