mirror of
https://github.com/ansible/awx.git
synced 2026-03-18 01:17:35 -02:30
Merge pull request #5832 from marshmalien/output-status-bar
Add host status bar Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -18,12 +18,12 @@ const ansi = new Ansi({
|
|||||||
stream: true,
|
stream: true,
|
||||||
colors: {
|
colors: {
|
||||||
0: '#000',
|
0: '#000',
|
||||||
1: '#A00',
|
1: '#A30000',
|
||||||
2: '#080',
|
2: '#486B00',
|
||||||
3: '#F0AD4E',
|
3: '#795600',
|
||||||
4: '#00A',
|
4: '#00A',
|
||||||
5: '#A0A',
|
5: '#A0A',
|
||||||
6: '#0AA',
|
6: '#004368',
|
||||||
7: '#AAA',
|
7: '#AAA',
|
||||||
8: '#555',
|
8: '#555',
|
||||||
9: '#F55',
|
9: '#F55',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('<JobEvent />', () => {
|
|||||||
expect(
|
expect(
|
||||||
lineText
|
lineText
|
||||||
.html()
|
.html()
|
||||||
.includes('<span style="color:#080">ok: [localhost]</span>')
|
.includes('<span style="color:#486B00">ok: [localhost]</span>')
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
AutoSizer,
|
AutoSizer,
|
||||||
@@ -7,23 +8,22 @@ import {
|
|||||||
List,
|
List,
|
||||||
} from 'react-virtualized';
|
} from 'react-virtualized';
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody } from '@components/Card';
|
||||||
|
|
||||||
import { JobsAPI } from '@api';
|
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import JobEvent from './JobEvent';
|
import JobEvent from './JobEvent';
|
||||||
import JobEventSkeleton from './JobEventSkeleton';
|
import JobEventSkeleton from './JobEventSkeleton';
|
||||||
import PageControls from './PageControls';
|
import PageControls from './PageControls';
|
||||||
import HostEventModal from './HostEventModal';
|
import HostEventModal from './HostEventModal';
|
||||||
|
import { HostStatusBar } from './shared';
|
||||||
|
import { JobsAPI } from '@api';
|
||||||
|
|
||||||
const OutputHeader = styled.div`
|
const OutputHeader = styled.div`
|
||||||
font-weight: var(--pf-global--FontWeight--bold);
|
font-weight: var(--pf-global--FontWeight--bold);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const OutputWrapper = styled.div`
|
const OutputWrapper = styled.div`
|
||||||
background-color: #fafafa;
|
background-color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
@@ -306,6 +306,7 @@ class JobOutput extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<OutputHeader>{job.name}</OutputHeader>
|
<OutputHeader>{job.name}</OutputHeader>
|
||||||
|
<HostStatusBar counts={job.host_status_counts} />
|
||||||
<PageControls
|
<PageControls
|
||||||
onScrollFirst={this.handleScrollFirst}
|
onScrollFirst={this.handleScrollFirst}
|
||||||
onScrollLast={this.handleScrollLast}
|
onScrollLast={this.handleScrollLast}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Badge, Tooltip } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const BarWrapper = styled.div`
|
||||||
|
background-color: #d7d7d7;
|
||||||
|
display: flex;
|
||||||
|
height: 5px;
|
||||||
|
margin: 24px 0;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BarSegment = styled.div`
|
||||||
|
background-color: ${props => 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: '#4CB140',
|
||||||
|
label: i18n._(t`OK`),
|
||||||
|
},
|
||||||
|
skipped: {
|
||||||
|
color: '#73BCF7',
|
||||||
|
label: i18n._(t`Skipped`),
|
||||||
|
},
|
||||||
|
changed: {
|
||||||
|
color: '#F0AB00',
|
||||||
|
label: i18n._(t`Changed`),
|
||||||
|
},
|
||||||
|
failures: {
|
||||||
|
color: '#C9190B',
|
||||||
|
label: i18n._(t`Failed`),
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
color: '#8F4700',
|
||||||
|
label: i18n._(t`Unreachable`),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const barSegments = Object.keys(hostStatus).map(key => {
|
||||||
|
const count = counts[key] || 0;
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={key}
|
||||||
|
content={
|
||||||
|
<TooltipContent>
|
||||||
|
{hostStatus[key].label}
|
||||||
|
<Badge isRead>{count}</Badge>
|
||||||
|
</TooltipContent>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<BarSegment key={key} color={hostStatus[key].color} count={count} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (noData) {
|
||||||
|
return (
|
||||||
|
<BarWrapper>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Host status information for this job is unavailable.`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<BarSegment count={1} />
|
||||||
|
</Tooltip>
|
||||||
|
</BarWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BarWrapper>{barSegments}</BarWrapper>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(HostStatusBar);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
import { HostStatusBar } from '.';
|
||||||
|
|
||||||
|
describe('<HostStatusBar />', () => {
|
||||||
|
let wrapper;
|
||||||
|
const mockCounts = {
|
||||||
|
ok: 5,
|
||||||
|
skipped: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mountWithContexts(<HostStatusBar counts={mockCounts} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 'Skipped', count: 1 },
|
||||||
|
{ label: 'Changed', count: 0 },
|
||||||
|
{ label: 'Failed', count: 0 },
|
||||||
|
{ label: 'Unreachable', count: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
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(<HostStatusBar />);
|
||||||
|
expect(wrapper.find('HostStatusBar__BarSegment').length).toBe(1);
|
||||||
|
expect(wrapper.find('TooltipContent').text()).toEqual(
|
||||||
|
'Host status information for this job is unavailable.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,3 +2,4 @@ export { default as JobEventLine } from './JobEventLine';
|
|||||||
export { default as JobEventLineToggle } from './JobEventLineToggle';
|
export { default as JobEventLineToggle } from './JobEventLineToggle';
|
||||||
export { default as JobEventLineNumber } from './JobEventLineNumber';
|
export { default as JobEventLineNumber } from './JobEventLineNumber';
|
||||||
export { default as JobEventLineText } from './JobEventLineText';
|
export { default as JobEventLineText } from './JobEventLineText';
|
||||||
|
export { default as HostStatusBar } from './HostStatusBar';
|
||||||
|
|||||||
Reference in New Issue
Block a user