diff --git a/awx/ui/src/components/StatusIcon/StatusIcon.js b/awx/ui/src/components/StatusIcon/StatusIcon.js
index 6f4e42c47e..71149f8753 100644
--- a/awx/ui/src/components/StatusIcon/StatusIcon.js
+++ b/awx/ui/src/components/StatusIcon/StatusIcon.js
@@ -1,136 +1,43 @@
import React from 'react';
import { string } from 'prop-types';
-import styled, { keyframes } from 'styled-components';
+import icons from './icons';
-const Pulse = keyframes`
- from {
- -webkit-transform:scale(1);
- }
- to {
- -webkit-transform:scale(0);
- }
-`;
+const green = '--pf-global--success-color--100';
+const red = '--pf-global--danger-color--100';
+const blue = '--pf-global--primary-color--100';
+const orange = '--pf-global--palette--orange-300';
+const gray = '--pf-global--Color--300';
+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`
- align-items: center;
- display: flex;
- flex-flow: column nowrap;
- height: 14px;
- margin: 5px 0;
- width: 14px;
-`;
-
-const WhiteTop = styled.div`
- border: 1px solid #b7b7b7;
- border-bottom: 0;
- 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 }) => (
-
- {status === 'running' && }
- {(status === 'new' ||
- status === 'pending' ||
- status === 'waiting' ||
- status === 'never updated') && }
- {(status === 'failed' || status === 'error' || status === 'canceled') && (
-
-
-
-
- )}
- {(status === 'successful' || status === 'ok') && (
-
-
-
-
- )}
- {status === 'changed' && (
-
-
-
-
- )}
- {status === 'skipped' && (
-
-
-
-
- )}
- {status === 'unreachable' && (
-
-
-
-
- )}
- {status}
-
-);
+function StatusIcon({ status, ...props }) {
+ const color = colors[status] || '--pf-chart-global--Fill--Color--500';
+ const Icon = icons[status];
+ return (
+
+ {Icon ? (
+
+
+
+ ) : null}
+
{status}
+
+ );
+}
StatusIcon.propTypes = {
status: string.isRequired,
diff --git a/awx/ui/src/components/StatusIcon/StatusIcon.test.js b/awx/ui/src/components/StatusIcon/StatusIcon.test.js
index dd552b3ef4..783bc8745f 100644
--- a/awx/ui/src/components/StatusIcon/StatusIcon.test.js
+++ b/awx/ui/src/components/StatusIcon/StatusIcon.test.js
@@ -6,53 +6,54 @@ describe('StatusIcon', () => {
test('renders the successful status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon SuccessfulTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
+ expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
});
+
test('renders running status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon RunningJob')).toHaveLength(1);
+ expect(wrapper.find('RunningIcon')).toHaveLength(1);
});
+
test('renders waiting status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon WaitingJob')).toHaveLength(1);
+ expect(wrapper.find('ClockIcon')).toHaveLength(1);
});
+
test('renders failed status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon FailedTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon FailedBottom')).toHaveLength(1);
+ expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
});
+
test('renders a successful status when host status is "ok"', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon SuccessfulTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
+ expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
});
+
test('renders "failed" host status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon FailedTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon FailedBottom')).toHaveLength(1);
+ expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
});
+
test('renders "changed" host status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon ChangedTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon ChangedBottom')).toHaveLength(1);
+ expect(wrapper.find('ExclamationTriangleIcon')).toHaveLength(1);
});
+
test('renders "skipped" host status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon SkippedTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon SkippedBottom')).toHaveLength(1);
+ expect(wrapper.find('MinusCircleIcon')).toHaveLength(1);
});
+
test('renders "unreachable" host status', () => {
const wrapper = mount();
expect(wrapper).toHaveLength(1);
- expect(wrapper.find('StatusIcon UnreachableTop')).toHaveLength(1);
- expect(wrapper.find('StatusIcon UnreachableBottom')).toHaveLength(1);
+ expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
});
});
diff --git a/awx/ui/src/components/StatusIcon/icons.js b/awx/ui/src/components/StatusIcon/icons.js
new file mode 100644
index 0000000000..6148c6945f
--- /dev/null
+++ b/awx/ui/src/components/StatusIcon/icons.js
@@ -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;
diff --git a/awx/ui/src/components/StatusLabel/StatusLabel.js b/awx/ui/src/components/StatusLabel/StatusLabel.js
index a8486f3de1..cb25d5d0ba 100644
--- a/awx/ui/src/components/StatusLabel/StatusLabel.js
+++ b/awx/ui/src/components/StatusLabel/StatusLabel.js
@@ -3,52 +3,23 @@ import React from 'react';
import { t } from '@lingui/macro';
import { oneOf } from 'prop-types';
import { Label, Tooltip } from '@patternfly/react-core';
-import {
- 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;
-`;
+import icons from '../StatusIcon/icons';
const colors = {
success: 'green',
successful: 'green',
+ ok: 'green',
healthy: 'green',
failed: 'red',
error: 'red',
+ unreachable: 'red',
running: 'blue',
pending: 'blue',
+ skipped: 'blue',
waiting: 'grey',
disabled: 'grey',
canceled: 'orange',
-};
-const icons = {
- success: CheckCircleIcon,
- healthy: CheckCircleIcon,
- successful: CheckCircleIcon,
- failed: ExclamationCircleIcon,
- error: ExclamationCircleIcon,
- running: RunningIcon,
- pending: ClockIcon,
- waiting: ClockIcon,
- disabled: MinusCircleIcon,
- canceled: ExclamationTriangleIcon,
+ changed: 'orange',
};
export default function StatusLabel({ status, tooltipContent = '' }) {
@@ -56,15 +27,19 @@ export default function StatusLabel({ status, tooltipContent = '' }) {
success: t`Success`,
healthy: t`Healthy`,
successful: t`Successful`,
+ ok: t`OK`,
failed: t`Failed`,
error: t`Error`,
+ unreachable: t`Unreachable`,
running: t`Running`,
pending: t`Pending`,
+ skipped: t`Skipped'`,
waiting: t`Waiting`,
disabled: t`Disabled`,
canceled: t`Canceled`,
+ changed: t`Changed`,
};
- const label = upperCaseStatus[status] || t`Undefined`;
+ const label = upperCaseStatus[status] || status;
const color = colors[status] || 'grey';
const Icon = icons[status];
@@ -91,13 +66,17 @@ StatusLabel.propTypes = {
status: oneOf([
'success',
'successful',
+ 'ok',
'healthy',
'failed',
'error',
+ 'unreachable',
'running',
'pending',
+ 'skipped',
'waiting',
'disabled',
'canceled',
+ 'changed',
]).isRequired,
};
diff --git a/awx/ui/src/screens/Job/JobDetail/JobDetail.js b/awx/ui/src/screens/Job/JobDetail/JobDetail.js
index 1f7b78101b..1c9d7cac90 100644
--- a/awx/ui/src/screens/Job/JobDetail/JobDetail.js
+++ b/awx/ui/src/screens/Job/JobDetail/JobDetail.js
@@ -21,11 +21,10 @@ import { VariablesDetail } from 'components/CodeEditor';
import DeleteButton from 'components/DeleteButton';
import ErrorDetail from 'components/ErrorDetail';
import { LaunchButton, ReLaunchDropDown } from 'components/LaunchButton';
-import StatusIcon from 'components/StatusIcon';
+import StatusLabel from 'components/StatusLabel';
import JobCancelButton from 'components/JobCancelButton';
import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
import { getJobModel, isJobRunning } from 'util/jobs';
-import { toTitleCase } from 'util/strings';
import { formatDateString } from 'util/dates';
import { Job } from 'types';
@@ -92,25 +91,6 @@ function JobDetail({ job, inventorySourceLabels }) {
{item.name}
);
- const buildProjectDetailValue = () => {
- if (projectUpdate) {
- return (
-
-
-
-
- {project.name}
-
- );
- }
- return (
-
-
- {project.name}
-
- );
- };
-
return (
@@ -121,10 +101,7 @@ function JobDetail({ job, inventorySourceLabels }) {
label={t`Status`}
value={
- {job.status && }
- {job.job_explanation
- ? job.job_explanation
- : toTitleCase(job.status)}
+ {job.status && }
}
/>
@@ -229,7 +206,7 @@ function JobDetail({ job, inventorySourceLabels }) {
value={
{source_project.status && (
-
+
)}
{source_project.name}
@@ -239,11 +216,26 @@ function JobDetail({ job, inventorySourceLabels }) {
/>
)}
{project && (
-
+ <>
+ {project.name}}
+ />
+
+
+
+ ) : (
+
+ )
+ }
+ />
+ >
)}
{scmBranch && (
', () => {
// StatusIcon adds visibly hidden accessibility text " successful "
assertDetail('Job ID', '2');
- assertDetail('Status', ' successful Successful');
+ assertDetail('Status', 'Successful');
assertDetail('Started', '8/8/2019, 7:24:18 PM');
assertDetail('Finished', '8/8/2019, 7:24:50 PM');
assertDetail('Job Template', mockJobData.summary_fields.job_template.name);
@@ -54,10 +54,7 @@ describe('', () => {
assertDetail('Job Type', 'Playbook Run');
assertDetail('Launched By', mockJobData.summary_fields.created_by.username);
assertDetail('Inventory', mockJobData.summary_fields.inventory.name);
- assertDetail(
- 'Project',
- ` successful ${mockJobData.summary_fields.project.name}`
- );
+ assertDetail('Project', mockJobData.summary_fields.project.name);
assertDetail('Revision', mockJobData.scm_revision);
assertDetail('Playbook', mockJobData.playbook);
assertDetail('Verbosity', '0 (Normal)');
@@ -98,16 +95,13 @@ describe('', () => {
).toEqual(true);
const statusDetail = wrapper.find('Detail[label="Status"]');
- expect(statusDetail.find('StatusIcon SuccessfulTop')).toHaveLength(1);
- expect(statusDetail.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
+ const statusLabel = statusDetail.find('StatusLabel');
+ expect(statusLabel.prop('status')).toEqual('successful');
- const projectStatusDetail = wrapper.find('Detail[label="Project"]');
- expect(projectStatusDetail.find('StatusIcon SuccessfulTop')).toHaveLength(
- 1
- );
- expect(
- projectStatusDetail.find('StatusIcon SuccessfulBottom')
- ).toHaveLength(1);
+ const projectStatusDetail = wrapper.find('Detail[label="Project Status"]');
+ expect(projectStatusDetail.find('StatusLabel')).toHaveLength(1);
+ const projectStatusLabel = statusDetail.find('StatusLabel');
+ expect(projectStatusLabel.prop('status')).toEqual('successful');
});
test('should not display finished date', () => {
@@ -537,7 +531,7 @@ describe('', () => {
webhook_guid: '',
};
wrapper = mountWithContexts();
- assertDetail('Status', ' successful Successful');
+ assertDetail('Status', 'Successful');
assertDetail('Started', '7/6/2021, 7:40:17 PM');
assertDetail('Finished', '7/6/2021, 7:40:42 PM');
assertDetail('Job Template', 'Sliced Job Template');
diff --git a/awx/ui/src/screens/Job/JobOutput/HostEventModal.js b/awx/ui/src/screens/Job/JobOutput/HostEventModal.js
index 322c9a6b0c..95d8ee989b 100644
--- a/awx/ui/src/screens/Job/JobOutput/HostEventModal.js
+++ b/awx/ui/src/screens/Job/JobOutput/HostEventModal.js
@@ -3,20 +3,12 @@ import { Modal, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
import PropTypes from 'prop-types';
import { t } from '@lingui/macro';
-import styled from 'styled-components';
import { encode } from 'html-entities';
-import StatusIcon from '../../../components/StatusIcon';
+import StatusLabel from '../../../components/StatusLabel';
import { DetailList, Detail } from '../../../components/DetailList';
import ContentEmpty from '../../../components/ContentEmpty';
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) => {
let status = null;
if (event.event === 'runner_on_unreachable') {
@@ -117,15 +109,13 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false }) {
style={{ alignItems: 'center', marginTop: '20px' }}
gutter="sm"
>
-
- {hostStatus ? : null}
- {hostEvent.host_name}
-
- }
- />
+
+ {hostStatus ? (
+ }
+ />
+ ) : null}
{
-// 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', () => {
test('initially renders successfully', () => {
const wrapper = shallow(
@@ -105,14 +93,14 @@ describe('HostEventModal', () => {
}
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(2, 'Task', 'command');
assertDetail(3, 'Module', 'command');
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 wrapper = mountWithContexts(
{
isOpen
/>
);
- const icon = wrapper.find('StatusIcon');
+ const icon = wrapper.find('StatusLabel');
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 wrapper = mountWithContexts(
{}} isOpen />
);
- const icon = wrapper.find('StatusIcon');
+ const icon = wrapper.find('StatusLabel');
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 = {
...hostEvent,
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.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 = {
...hostEvent,
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.find('StatusIcon FailedTop').length).toBe(1);
- expect(icon.find('StatusIcon FailedBottom').length).toBe(1);
});
test('should display JSON tab content on tab click', () => {
diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
index 4e48fc169b..1a26cd9409 100644
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.js
+++ b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
@@ -16,7 +16,7 @@ import { CardBody as _CardBody } from 'components/Card';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import ErrorDetail from 'components/ErrorDetail';
-import StatusIcon from 'components/StatusIcon';
+import StatusLabel from 'components/StatusLabel';
import { JobEventsAPI } from 'api';
import { getJobModel, isJobRunning } from 'util/jobs';
@@ -51,7 +51,7 @@ const HeaderTitle = styled.div`
display: inline-flex;
align-items: center;
h1 {
- margin-left: 10px;
+ margin-right: 10px;
font-weight: var(--pf-global--FontWeight--bold);
}
`;
@@ -669,8 +669,8 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
)}
-
{job.name}
+
-
- {job.name}
+ {job.name}
+