mirror of
https://github.com/ansible/awx.git
synced 2026-01-23 23:41:23 -03:30
Merge pull request #4442 from mabashian/4225-sparkline-templates
Add Sparkline to templates list Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
commit
8fef029bc3
@ -353,7 +353,7 @@ function projectsListController (
|
||||
};
|
||||
|
||||
function buildTooltips (project) {
|
||||
project.statusIcon = getStatusIcon(project);
|
||||
project.statusIcon = getJobStatusIcon(project);
|
||||
project.statusTip = getStatusTooltip(project);
|
||||
project.scm_update_tooltip = vm.strings.get('update.GET_LATEST');
|
||||
project.scm_update_disabled = false;
|
||||
@ -408,7 +408,7 @@ function projectsListController (
|
||||
};
|
||||
}
|
||||
|
||||
function getStatusIcon (project) {
|
||||
function getJobStatusIcon (project) {
|
||||
let icon = 'none';
|
||||
switch (project.status) {
|
||||
case 'n/a':
|
||||
|
||||
86
awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx
Normal file
86
awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { string } from 'prop-types';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
const Pulse = keyframes`
|
||||
from {
|
||||
-webkit-transform:scale(1);
|
||||
}
|
||||
to {
|
||||
-webkit-transform:scale(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
`;
|
||||
|
||||
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.div`
|
||||
border: 1px solid #b7b7b7;
|
||||
border-top: 0;
|
||||
background: #ffffff;
|
||||
`;
|
||||
|
||||
const FailedTop = styled.div`
|
||||
border: 1px solid #b7b7b7;
|
||||
border-bottom: 0;
|
||||
background: #ffffff;
|
||||
`;
|
||||
|
||||
const FailedBottom = styled.div`
|
||||
background-color: #d9534f;
|
||||
`;
|
||||
|
||||
const JobStatusIcon = ({ status, ...props }) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{status === 'running' && <RunningJob />}
|
||||
{(status === 'new' || status === 'pending' || status === 'waiting') && (
|
||||
<WaitingJob />
|
||||
)}
|
||||
{(status === 'failed' || status === 'error' || status === 'canceled') && (
|
||||
<FinishedJob>
|
||||
<FailedTop />
|
||||
<FailedBottom />
|
||||
</FinishedJob>
|
||||
)}
|
||||
{status === 'successful' && (
|
||||
<FinishedJob>
|
||||
<SuccessfulTop />
|
||||
<SuccessfulBottom />
|
||||
</FinishedJob>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
JobStatusIcon.propTypes = {
|
||||
status: string.isRequired,
|
||||
};
|
||||
|
||||
export default JobStatusIcon;
|
||||
28
awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx
Normal file
28
awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import JobStatusIcon from './JobStatusIcon';
|
||||
|
||||
describe('JobStatusIcon', () => {
|
||||
test('renders the successful job', () => {
|
||||
const wrapper = mount(<JobStatusIcon status="successful" />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__SuccessfulTop')).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__SuccessfulBottom')).toHaveLength(1);
|
||||
});
|
||||
test('renders running job', () => {
|
||||
const wrapper = mount(<JobStatusIcon status="running" />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__RunningJob')).toHaveLength(1);
|
||||
});
|
||||
test('renders waiting job', () => {
|
||||
const wrapper = mount(<JobStatusIcon status="waiting" />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__WaitingJob')).toHaveLength(1);
|
||||
});
|
||||
test('renders failed job', () => {
|
||||
const wrapper = mount(<JobStatusIcon status="failed" />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__FailedTop')).toHaveLength(1);
|
||||
expect(wrapper.find('JobStatusIcon__FailedBottom')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
50
awx/ui_next/src/components/Sparkline/Sparkline.jsx
Normal file
50
awx/ui_next/src/components/Sparkline/Sparkline.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { arrayOf, object } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { Link as _Link } from 'react-router-dom';
|
||||
import { JobStatusIcon } from '@components/Sparkline';
|
||||
import { Tooltip } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
import { t } from '@lingui/macro';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
const Link = styled(props => <_Link {...props} />)`
|
||||
margin-right: 5px;
|
||||
`;
|
||||
/* eslint-enable react/jsx-pascal-case */
|
||||
|
||||
const Sparkline = ({ i18n, jobs }) => {
|
||||
const generateTooltip = job => (
|
||||
<Fragment>
|
||||
<div>
|
||||
{i18n._(t`JOB ID:`)} {job.id}
|
||||
</div>
|
||||
<div>
|
||||
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
|
||||
</div>
|
||||
{job.finished && (
|
||||
<div>
|
||||
{i18n._(t`FINISHED:`)} {job.finished}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return jobs.map(job => (
|
||||
<Tooltip position="top" content={generateTooltip(job)} key={job.id}>
|
||||
<Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}>
|
||||
<JobStatusIcon status={job.status} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
));
|
||||
};
|
||||
|
||||
Sparkline.propTypes = {
|
||||
jobs: arrayOf(object),
|
||||
};
|
||||
Sparkline.defaultProps = {
|
||||
jobs: [],
|
||||
};
|
||||
|
||||
export default withI18n()(Sparkline);
|
||||
30
awx/ui_next/src/components/Sparkline/Sparkline.test.jsx
Normal file
30
awx/ui_next/src/components/Sparkline/Sparkline.test.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
|
||||
import Sparkline from './Sparkline';
|
||||
|
||||
describe('Sparkline', () => {
|
||||
test('renders the expected content', () => {
|
||||
const wrapper = mount(<Sparkline />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
test('renders an icon with tooltips and links for each job', () => {
|
||||
const jobs = [
|
||||
{
|
||||
id: 1,
|
||||
status: 'successful',
|
||||
finished: '2019-08-08T15:27:57.320120Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
status: 'failed',
|
||||
finished: '2019-08-09T15:27:57.320120Z',
|
||||
},
|
||||
];
|
||||
const wrapper = mountWithContexts(<Sparkline jobs={jobs} />);
|
||||
expect(wrapper.find('JobStatusIcon')).toHaveLength(2);
|
||||
expect(wrapper.find('Tooltip')).toHaveLength(2);
|
||||
expect(wrapper.find('Link')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
2
awx/ui_next/src/components/Sparkline/index.js
Normal file
2
awx/ui_next/src/components/Sparkline/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Sparkline } from './Sparkline';
|
||||
export { default as JobStatusIcon } from './JobStatusIcon';
|
||||
@ -15,7 +15,7 @@ import RoutedTabs from '@components/RoutedTabs';
|
||||
|
||||
import JobDetail from './JobDetail';
|
||||
import JobOutput from './JobOutput';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from './constants';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||
|
||||
class Job extends Component {
|
||||
constructor(props) {
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import DataListCell from '@components/DataListCell';
|
||||
import VerticalSeparator from '@components/VerticalSeparator';
|
||||
import { toTitleCase } from '@util/strings';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../constants';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../../constants';
|
||||
|
||||
class JobListItem extends Component {
|
||||
render() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { UnifiedJobsAPI } from '@api';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from './constants';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||
|
||||
class JobTypeRedirect extends Component {
|
||||
static defaultProps = {
|
||||
|
||||
@ -6,7 +6,7 @@ import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
||||
import Job from './Job';
|
||||
import JobTypeRedirect from './JobTypeRedirect';
|
||||
import JobList from './JobList/JobList';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from './constants';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||
|
||||
class Jobs extends Component {
|
||||
constructor(props) {
|
||||
|
||||
@ -16,6 +16,7 @@ import styled from 'styled-components';
|
||||
import DataListCell from '@components/DataListCell';
|
||||
import LaunchButton from '@components/LaunchButton';
|
||||
import VerticalSeparator from '@components/VerticalSeparator';
|
||||
import { Sparkline } from '@components/Sparkline';
|
||||
import { toTitleCase } from '@util/strings';
|
||||
|
||||
const StyledButton = styled(PFButton)`
|
||||
@ -56,6 +57,9 @@ class TemplateListItem extends Component {
|
||||
<DataListCell key="type">
|
||||
{toTitleCase(template.type)}
|
||||
</DataListCell>,
|
||||
<DataListCell key="sparkline">
|
||||
<Sparkline jobs={template.summary_fields.recent_jobs} />
|
||||
</DataListCell>,
|
||||
<DataListCell lastcolumn="true" key="launch">
|
||||
{canLaunch && template.type === 'job_template' && (
|
||||
<Tooltip content={i18n._(t`Launch`)} position="top">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user