diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 15775c56ec..30e9eed05f 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -1,3 +1,4 @@
+import React, { Component } from 'react';
import styled from 'styled-components';
import {
AutoSizer,
@@ -7,16 +8,15 @@ import {
List,
} from 'react-virtualized';
-import React, { Component } from 'react';
import { CardBody } from '@components/Card';
-
-import { JobsAPI } from '@api';
import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import JobEvent from './JobEvent';
import JobEventSkeleton from './JobEventSkeleton';
import PageControls from './PageControls';
import HostEventModal from './HostEventModal';
+import { HostStatusBar } from './shared';
+import { JobsAPI } from '@api';
const OutputHeader = styled.div`
font-weight: var(--pf-global--FontWeight--bold);
@@ -306,6 +306,7 @@ class JobOutput extends Component {
/>
)}
{job.name}
+
props.color || 'inherit'};
+ flex-grow: ${props => props.count || 0};
+`;
+
+const TooltipContent = styled.div`
+ align-items: center;
+ display: flex;
+
+ span.pf-c-badge {
+ margin-left: 10px;
+ }
+`;
+
+const HostStatusBar = ({ i18n, counts = {} }) => {
+ const noData = Object.keys(counts).length === 0;
+ const hostStatus = {
+ ok: {
+ color: '#92D400',
+ label: i18n._(t`OK`),
+ },
+ dark: {
+ color: '#470000',
+ label: i18n._(t`Unreachable`),
+ },
+ failures: {
+ color: '#C9190B',
+ label: i18n._(t`Failed`),
+ },
+ changed: {
+ color: '#F0AB00',
+ label: i18n._(t`Changed`),
+ },
+ skipped: {
+ color: '#73BCF7',
+ label: i18n._(t`Skipped`),
+ },
+ };
+
+ const barSegments = Object.keys(hostStatus).map(key => {
+ const count = counts[key] || 0;
+ return (
+
+ {hostStatus[key].label}
+ {count}
+
+ }
+ >
+
+
+ );
+ });
+
+ if (noData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return {barSegments};
+};
+
+export default withI18n()(HostStatusBar);
diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/HostStatusBar.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/HostStatusBar.test.jsx
new file mode 100644
index 0000000000..d7629c6041
--- /dev/null
+++ b/awx/ui_next/src/screens/Job/JobOutput/shared/HostStatusBar.test.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import { HostStatusBar } from '.';
+
+describe('', () => {
+ let wrapper;
+ const mockCounts = {
+ ok: 5,
+ skipped: 1,
+ };
+
+ beforeEach(() => {
+ wrapper = mountWithContexts();
+ });
+
+ afterEach(() => {
+ wrapper.unmount();
+ });
+
+ test('initially renders without crashing', () => {
+ expect(wrapper.length).toBe(1);
+ });
+
+ test('should render five bar segments', () => {
+ expect(wrapper.find('HostStatusBar__BarSegment').length).toBe(5);
+ });
+
+ test('tooltips should display host status and count', () => {
+ const tooltips = wrapper.find('TooltipContent');
+ const expectedContent = [
+ { label: 'OK', count: 5 },
+ { label: 'Unreachable', count: 0 },
+ { label: 'Failed', count: 0 },
+ { label: 'Changed', count: 0 },
+ { label: 'Skipped', count: 1 },
+ ];
+
+ tooltips.forEach((tooltip, index) => {
+ expect(tooltip.text()).toEqual(
+ `${expectedContent[index].label}${expectedContent[index].count}`
+ );
+ });
+ });
+
+ test('empty host counts should display tooltip and one bar segment', () => {
+ wrapper = mountWithContexts();
+ expect(wrapper.find('HostStatusBar__BarSegment').length).toBe(1);
+ expect(wrapper.find('TooltipContent').text()).toEqual(
+ 'Host status information for this job is unavailable.'
+ );
+ });
+});
diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/index.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/index.jsx
index d0edeb8e1f..58c72834e9 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/shared/index.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/shared/index.jsx
@@ -2,3 +2,4 @@ export { default as JobEventLine } from './JobEventLine';
export { default as JobEventLineToggle } from './JobEventLineToggle';
export { default as JobEventLineNumber } from './JobEventLineNumber';
export { default as JobEventLineText } from './JobEventLineText';
+export { default as HostStatusBar } from './HostStatusBar';