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