From 19d2c8c6347a82bb0b2dcd1c70bc01b791fb3ee8 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 6 Aug 2019 15:51:11 -0400 Subject: [PATCH 1/6] Adds sparkline to templates list --- .../projects/projectsList.controller.js | 4 +- .../components/Sparkline/JobStatusIcon.jsx | 128 ++++++++++++++++++ .../src/components/Sparkline/Sparkline.jsx | 41 ++++++ awx/ui_next/src/components/Sparkline/index.js | 2 + .../src/{screens/Job => }/constants.js | 0 awx/ui_next/src/screens/Job/Job.jsx | 2 +- .../src/screens/Job/JobList/JobListItem.jsx | 2 +- .../src/screens/Job/JobTypeRedirect.jsx | 2 +- awx/ui_next/src/screens/Job/Jobs.jsx | 2 +- .../TemplateList/TemplateListItem.jsx | 6 + 10 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx create mode 100644 awx/ui_next/src/components/Sparkline/Sparkline.jsx create mode 100644 awx/ui_next/src/components/Sparkline/index.js rename awx/ui_next/src/{screens/Job => }/constants.js (100%) diff --git a/awx/ui/client/features/projects/projectsList.controller.js b/awx/ui/client/features/projects/projectsList.controller.js index a295d6e52b..66605f8f13 100644 --- a/awx/ui/client/features/projects/projectsList.controller.js +++ b/awx/ui/client/features/projects/projectsList.controller.js @@ -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': diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx new file mode 100644 index 0000000000..609a4939ed --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx @@ -0,0 +1,128 @@ +import React, { Fragment } from 'react'; +import { node, object, string } from 'prop-types'; +import { Link } from 'react-router-dom'; +import styled, { keyframes } from 'styled-components'; +import { Tooltip } from '@patternfly/react-core'; + +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 = ({ job, link, tooltip, ...props }) => { + let Icon = ( + + { job.status === 'running' && } + { (job.status === 'new' + || job.status === 'pending' + || job.status === 'waiting') + && + } + { (job.status === 'failed' + || job.status === 'error' + || job.status === 'canceled') + && ( + + + + + ) + } + { job.status === 'successful' && ( + + + + + )} + + ); + + if (link) { + Icon = ( + + {Icon} + + ); + } + + if (tooltip) { + return ( +
+ + {Icon} + +
+ ); + } + + return ( +
+ {Icon} +
+ ); +} + +JobStatusIcon.propTypes = { + job: object.isRequired, + link: string, + tooltip: node +}; + +JobStatusIcon.defaultProps = { + link: null, + tooltip: null, +}; + +export default JobStatusIcon; diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx new file mode 100644 index 0000000000..4c9a97448f --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -0,0 +1,41 @@ +import React, { Fragment } from 'react'; +import { arrayOf, object } from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { JobStatusIcon as _JobStatusIcon } from '@components/Sparkline'; +import styled from 'styled-components'; +import { t } from '@lingui/macro'; +import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; + +const JobStatusIcon = styled(props => <_JobStatusIcon {...props} />)` + margin-right: 5px; +`; + +const Sparkline = ({ i18n, jobs }) => { + const generateTooltip = (job) => ( + +
{i18n._(t`JOB ID:`)} {job.id}
+
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
+
{i18n._(t`FINISHED:`)} {job.finished}
+
+ ); + + return ( + (jobs.map(job => ( + + ))) + ) +}; + +Sparkline.propTypes = { + jobs: arrayOf(object), +}; +Sparkline.defaultProps = { + jobs: [], +}; + +export default withI18n()(Sparkline); diff --git a/awx/ui_next/src/components/Sparkline/index.js b/awx/ui_next/src/components/Sparkline/index.js new file mode 100644 index 0000000000..6c37734e51 --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/index.js @@ -0,0 +1,2 @@ +export { default as Sparkline } from './Sparkline'; +export { default as JobStatusIcon } from './JobStatusIcon'; \ No newline at end of file diff --git a/awx/ui_next/src/screens/Job/constants.js b/awx/ui_next/src/constants.js similarity index 100% rename from awx/ui_next/src/screens/Job/constants.js rename to awx/ui_next/src/constants.js diff --git a/awx/ui_next/src/screens/Job/Job.jsx b/awx/ui_next/src/screens/Job/Job.jsx index 0352a7b260..313f051cad 100644 --- a/awx/ui_next/src/screens/Job/Job.jsx +++ b/awx/ui_next/src/screens/Job/Job.jsx @@ -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) { diff --git a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx b/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx index 7cfc4dd5ac..93c67e7070 100644 --- a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx +++ b/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx @@ -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() { diff --git a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx index 87738c6ce8..20ba8b1bc0 100644 --- a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx +++ b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx @@ -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 = { diff --git a/awx/ui_next/src/screens/Job/Jobs.jsx b/awx/ui_next/src/screens/Job/Jobs.jsx index 9279a9f511..8c86472a10 100644 --- a/awx/ui_next/src/screens/Job/Jobs.jsx +++ b/awx/ui_next/src/screens/Job/Jobs.jsx @@ -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) { diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index bb31945c46..947a04a72e 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -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,11 @@ class TemplateListItem extends Component { {toTitleCase(template.type)} , + + + , {canLaunch && template.type === 'job_template' && ( From 37e5b6b13444846623a5f0697135054b0d9315aa Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 7 Aug 2019 14:44:16 -0400 Subject: [PATCH 2/6] Add sparkline tests --- .../Sparkline/JobStatusIcon.test.jsx | 61 +++++++++++++++++++ .../components/Sparkline/Sparkline.test.jsx | 25 ++++++++ 2 files changed, 86 insertions(+) create mode 100644 awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx create mode 100644 awx/ui_next/src/components/Sparkline/Sparkline.test.jsx diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx new file mode 100644 index 0000000000..914c574cc7 --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; + +import JobStatusIcon from './JobStatusIcon'; + +describe('JobStatusIcon', () => { + const job = { + id: 1, + status: 'successful' + }; + + test('renders the expected content', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('Tooltip')).toHaveLength(0); + expect(wrapper.find('Link')).toHaveLength(0); + }); + test('renders with tooltip if tooltip passed', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('Tooltip')).toHaveLength(1); + expect(wrapper.find('Link')).toHaveLength(0); + }); + test('renders with link if link passed', () => { + const wrapper = mountWithContexts(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('Tooltip')).toHaveLength(0); + expect(wrapper.find('Link')).toHaveLength(1); + }); + test('renders running job', () => { + const runningJob = { + id: 2, + status: 'running' + }; + const wrapper = mount(); + expect(wrapper.find('JobStatusIcon__RunningJob')).toHaveLength(1); + }); + test('renders waiting job', () => { + const waitingJob = { + id: 3, + status: 'waiting' + }; + const wrapper = mount(); + expect(wrapper.find('JobStatusIcon__WaitingJob')).toHaveLength(1); + }); + test('renders failed job', () => { + const failedJob = { + id: 4, + status: 'failed' + }; + const wrapper = mount(); + expect(wrapper.find('JobStatusIcon__FailedTop')).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__FailedBottom')).toHaveLength(1); + }); + test('renders successful job', () => { + const wrapper = mount(); + expect(wrapper.find('JobStatusIcon__SuccessfulTop')).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__SuccessfulBottom')).toHaveLength(1); + }); +}); \ No newline at end of file diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx new file mode 100644 index 0000000000..6417acd7d2 --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx @@ -0,0 +1,25 @@ +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(); + expect(wrapper).toHaveLength(1); + }); + test('renders an icon for each job', () => { + const jobs = [ + { + id: 1, + status: 'successful' + }, { + id: 2, + status: 'failed' + } + ] + const wrapper = mountWithContexts(); + expect(wrapper.find('JobStatusIcon')).toHaveLength(2); + }); +}); From a57f2ca2bf9e7a6cfe962646371330cc2a4a20a7 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 7 Aug 2019 14:46:41 -0400 Subject: [PATCH 3/6] Run prettier --- .../components/Sparkline/JobStatusIcon.jsx | 65 +++++++------------ .../Sparkline/JobStatusIcon.test.jsx | 14 ++-- .../src/components/Sparkline/Sparkline.jsx | 32 +++++---- .../components/Sparkline/Sparkline.test.jsx | 11 ++-- awx/ui_next/src/components/Sparkline/index.js | 2 +- .../TemplateList/TemplateListItem.jsx | 4 +- 6 files changed, 59 insertions(+), 69 deletions(-) diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx index 609a4939ed..ce6713e90d 100644 --- a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx @@ -21,10 +21,7 @@ const Wrapper = styled.div` const RunningJob = styled(Wrapper)` background-color: #5cb85c; padding-right: 0px; - text-shadow: - -1px -1px 0 #ffffff, - 1px -1px 0 #ffffff, - -1px 1px 0 #ffffff, + 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; `; @@ -58,43 +55,35 @@ const FailedTop = styled.div` `; const FailedBottom = styled.div` - background-color: #D9534F; + background-color: #d9534f; `; const JobStatusIcon = ({ job, link, tooltip, ...props }) => { let Icon = ( - { job.status === 'running' && } - { (job.status === 'new' - || job.status === 'pending' - || job.status === 'waiting') - && - } - { (job.status === 'failed' - || job.status === 'error' - || job.status === 'canceled') - && ( - - - - - ) - } - { job.status === 'successful' && ( - - - - - )} - + {job.status === 'running' && } + {(job.status === 'new' || + job.status === 'pending' || + job.status === 'waiting') && } + {(job.status === 'failed' || + job.status === 'error' || + job.status === 'canceled') && ( + + + + + )} + {job.status === 'successful' && ( + + + + + )} + ); if (link) { - Icon = ( - - {Icon} - - ); + Icon = {Icon}; } if (tooltip) { @@ -107,17 +96,13 @@ const JobStatusIcon = ({ job, link, tooltip, ...props }) => { ); } - return ( -
- {Icon} -
- ); -} + return
{Icon}
; +}; JobStatusIcon.propTypes = { job: object.isRequired, link: string, - tooltip: node + tooltip: node, }; JobStatusIcon.defaultProps = { diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx index 914c574cc7..ab915db6d5 100644 --- a/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx @@ -7,7 +7,7 @@ import JobStatusIcon from './JobStatusIcon'; describe('JobStatusIcon', () => { const job = { id: 1, - status: 'successful' + status: 'successful', }; test('renders the expected content', () => { @@ -23,7 +23,9 @@ describe('JobStatusIcon', () => { expect(wrapper.find('Link')).toHaveLength(0); }); test('renders with link if link passed', () => { - const wrapper = mountWithContexts(); + const wrapper = mountWithContexts( + + ); expect(wrapper).toHaveLength(1); expect(wrapper.find('Tooltip')).toHaveLength(0); expect(wrapper.find('Link')).toHaveLength(1); @@ -31,7 +33,7 @@ describe('JobStatusIcon', () => { test('renders running job', () => { const runningJob = { id: 2, - status: 'running' + status: 'running', }; const wrapper = mount(); expect(wrapper.find('JobStatusIcon__RunningJob')).toHaveLength(1); @@ -39,7 +41,7 @@ describe('JobStatusIcon', () => { test('renders waiting job', () => { const waitingJob = { id: 3, - status: 'waiting' + status: 'waiting', }; const wrapper = mount(); expect(wrapper.find('JobStatusIcon__WaitingJob')).toHaveLength(1); @@ -47,7 +49,7 @@ describe('JobStatusIcon', () => { test('renders failed job', () => { const failedJob = { id: 4, - status: 'failed' + status: 'failed', }; const wrapper = mount(); expect(wrapper.find('JobStatusIcon__FailedTop')).toHaveLength(1); @@ -58,4 +60,4 @@ describe('JobStatusIcon', () => { expect(wrapper.find('JobStatusIcon__SuccessfulTop')).toHaveLength(1); expect(wrapper.find('JobStatusIcon__SuccessfulBottom')).toHaveLength(1); }); -}); \ No newline at end of file +}); diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx index 4c9a97448f..9872caeb5e 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -11,24 +11,28 @@ const JobStatusIcon = styled(props => <_JobStatusIcon {...props} />)` `; const Sparkline = ({ i18n, jobs }) => { - const generateTooltip = (job) => ( + const generateTooltip = job => ( -
{i18n._(t`JOB ID:`)} {job.id}
-
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
-
{i18n._(t`FINISHED:`)} {job.finished}
+
+ {i18n._(t`JOB ID:`)} {job.id} +
+
+ {i18n._(t`STATUS:`)} {job.status.toUpperCase()} +
+
+ {i18n._(t`FINISHED:`)} {job.finished} +
); - return ( - (jobs.map(job => ( - - ))) - ) + return jobs.map(job => ( + + )); }; Sparkline.propTypes = { diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx index 6417acd7d2..427709577b 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx @@ -13,12 +13,13 @@ describe('Sparkline', () => { const jobs = [ { id: 1, - status: 'successful' - }, { + status: 'successful', + }, + { id: 2, - status: 'failed' - } - ] + status: 'failed', + }, + ]; const wrapper = mountWithContexts(); expect(wrapper.find('JobStatusIcon')).toHaveLength(2); }); diff --git a/awx/ui_next/src/components/Sparkline/index.js b/awx/ui_next/src/components/Sparkline/index.js index 6c37734e51..f33b83ae6c 100644 --- a/awx/ui_next/src/components/Sparkline/index.js +++ b/awx/ui_next/src/components/Sparkline/index.js @@ -1,2 +1,2 @@ export { default as Sparkline } from './Sparkline'; -export { default as JobStatusIcon } from './JobStatusIcon'; \ No newline at end of file +export { default as JobStatusIcon } from './JobStatusIcon'; diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index 947a04a72e..964cc397d2 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -58,9 +58,7 @@ class TemplateListItem extends Component { {toTitleCase(template.type)}
, - + , {canLaunch && template.type === 'job_template' && ( From 6a7481a27c5c27454a2e5e6856203f53458eeb0f Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 7 Aug 2019 14:52:40 -0400 Subject: [PATCH 4/6] Prettier again --- awx/ui_next/src/components/Sparkline/Sparkline.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx index 9872caeb5e..7883dbc4e4 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -19,9 +19,11 @@ const Sparkline = ({ i18n, jobs }) => {
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
-
- {i18n._(t`FINISHED:`)} {job.finished} -
+ {job.finished && ( +
+ {i18n._(t`FINISHED:`)} {job.finished} +
+ )} ); From fba0da4c58b569ae9cb9dc8a7622b492b1fcc4c2 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 7 Aug 2019 16:16:57 -0400 Subject: [PATCH 5/6] Fix linting --- awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx | 7 +++++-- awx/ui_next/src/components/Sparkline/Sparkline.jsx | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx index ce6713e90d..3a1360a6dd 100644 --- a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx @@ -1,5 +1,5 @@ import React, { Fragment } from 'react'; -import { node, object, string } from 'prop-types'; +import { node, number, shape, string } from 'prop-types'; import { Link } from 'react-router-dom'; import styled, { keyframes } from 'styled-components'; import { Tooltip } from '@patternfly/react-core'; @@ -100,7 +100,10 @@ const JobStatusIcon = ({ job, link, tooltip, ...props }) => { }; JobStatusIcon.propTypes = { - job: object.isRequired, + job: shape({ + id: number.isRequired, + status: string.isRequired, + }).isRequired, link: string, tooltip: node, }; diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx index 7883dbc4e4..0456b4eaa7 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -6,9 +6,11 @@ import styled from 'styled-components'; import { t } from '@lingui/macro'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; +/* eslint-disable react/jsx-pascal-case */ const JobStatusIcon = styled(props => <_JobStatusIcon {...props} />)` margin-right: 5px; `; +/* eslint-enable react/jsx-pascal-case */ const Sparkline = ({ i18n, jobs }) => { const generateTooltip = job => ( From 3d98d98d3c1296f85cacdd06b97ce8bd8686591f Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 8 Aug 2019 11:53:47 -0400 Subject: [PATCH 6/6] Moves tooltip and link logic out to the sparkline from the job status icon --- .../components/Sparkline/JobStatusIcon.jsx | 56 ++++----------- .../Sparkline/JobStatusIcon.test.jsx | 71 +++++-------------- .../src/components/Sparkline/Sparkline.jsx | 17 ++--- .../components/Sparkline/Sparkline.test.jsx | 6 +- 4 files changed, 45 insertions(+), 105 deletions(-) diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx index 3a1360a6dd..8a462f72ed 100644 --- a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx @@ -1,8 +1,6 @@ -import React, { Fragment } from 'react'; -import { node, number, shape, string } from 'prop-types'; -import { Link } from 'react-router-dom'; +import React from 'react'; +import { string } from 'prop-types'; import styled, { keyframes } from 'styled-components'; -import { Tooltip } from '@patternfly/react-core'; const Pulse = keyframes` from { @@ -58,59 +56,31 @@ const FailedBottom = styled.div` background-color: #d9534f; `; -const JobStatusIcon = ({ job, link, tooltip, ...props }) => { - let Icon = ( - - {job.status === 'running' && } - {(job.status === 'new' || - job.status === 'pending' || - job.status === 'waiting') && } - {(job.status === 'failed' || - job.status === 'error' || - job.status === 'canceled') && ( +const JobStatusIcon = ({ status, ...props }) => { + return ( +
+ {status === 'running' && } + {(status === 'new' || status === 'pending' || status === 'waiting') && ( + + )} + {(status === 'failed' || status === 'error' || status === 'canceled') && ( )} - {job.status === 'successful' && ( + {status === 'successful' && ( )} - +
); - - if (link) { - Icon = {Icon}; - } - - if (tooltip) { - return ( -
- - {Icon} - -
- ); - } - - return
{Icon}
; }; JobStatusIcon.propTypes = { - job: shape({ - id: number.isRequired, - status: string.isRequired, - }).isRequired, - link: string, - tooltip: node, -}; - -JobStatusIcon.defaultProps = { - link: null, - tooltip: null, + status: string.isRequired, }; export default JobStatusIcon; diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx index ab915db6d5..204d305394 100644 --- a/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.test.jsx @@ -1,63 +1,28 @@ import React from 'react'; import { mount } from 'enzyme'; -import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; - import JobStatusIcon from './JobStatusIcon'; describe('JobStatusIcon', () => { - const job = { - id: 1, - status: 'successful', - }; - - test('renders the expected content', () => { - const wrapper = mount(); + test('renders the successful job', () => { + const wrapper = mount(); expect(wrapper).toHaveLength(1); - expect(wrapper.find('Tooltip')).toHaveLength(0); - expect(wrapper.find('Link')).toHaveLength(0); - }); - test('renders with tooltip if tooltip passed', () => { - const wrapper = mount(); - expect(wrapper).toHaveLength(1); - expect(wrapper.find('Tooltip')).toHaveLength(1); - expect(wrapper.find('Link')).toHaveLength(0); - }); - test('renders with link if link passed', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper).toHaveLength(1); - expect(wrapper.find('Tooltip')).toHaveLength(0); - expect(wrapper.find('Link')).toHaveLength(1); - }); - test('renders running job', () => { - const runningJob = { - id: 2, - status: 'running', - }; - const wrapper = mount(); - expect(wrapper.find('JobStatusIcon__RunningJob')).toHaveLength(1); - }); - test('renders waiting job', () => { - const waitingJob = { - id: 3, - status: 'waiting', - }; - const wrapper = mount(); - expect(wrapper.find('JobStatusIcon__WaitingJob')).toHaveLength(1); - }); - test('renders failed job', () => { - const failedJob = { - id: 4, - status: 'failed', - }; - const wrapper = mount(); - expect(wrapper.find('JobStatusIcon__FailedTop')).toHaveLength(1); - expect(wrapper.find('JobStatusIcon__FailedBottom')).toHaveLength(1); - }); - test('renders successful job', () => { - const wrapper = mount(); expect(wrapper.find('JobStatusIcon__SuccessfulTop')).toHaveLength(1); expect(wrapper.find('JobStatusIcon__SuccessfulBottom')).toHaveLength(1); }); + test('renders running job', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__RunningJob')).toHaveLength(1); + }); + test('renders waiting job', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__WaitingJob')).toHaveLength(1); + }); + test('renders failed job', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__FailedTop')).toHaveLength(1); + expect(wrapper.find('JobStatusIcon__FailedBottom')).toHaveLength(1); + }); }); diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx index 0456b4eaa7..5291eeca85 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -1,13 +1,15 @@ import React, { Fragment } from 'react'; import { arrayOf, object } from 'prop-types'; import { withI18n } from '@lingui/react'; -import { JobStatusIcon as _JobStatusIcon } from '@components/Sparkline'; +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 JobStatusIcon = styled(props => <_JobStatusIcon {...props} />)` +const Link = styled(props => <_Link {...props} />)` margin-right: 5px; `; /* eslint-enable react/jsx-pascal-case */ @@ -30,12 +32,11 @@ const Sparkline = ({ i18n, jobs }) => { ); return jobs.map(job => ( - + + + + + )); }; diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx index 427709577b..e39003a841 100644 --- a/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx +++ b/awx/ui_next/src/components/Sparkline/Sparkline.test.jsx @@ -9,18 +9,22 @@ describe('Sparkline', () => { const wrapper = mount(); expect(wrapper).toHaveLength(1); }); - test('renders an icon for each job', () => { + 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(); expect(wrapper.find('JobStatusIcon')).toHaveLength(2); + expect(wrapper.find('Tooltip')).toHaveLength(2); + expect(wrapper.find('Link')).toHaveLength(2); }); });