From 4fe558392ab25f78c1f21bc763a584ad005ad71b Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 12 Jun 2019 16:34:31 -0400 Subject: [PATCH 1/6] Update organization card header and tabs to use styled-components --- src/app.scss | 9 ----- src/components/Tabs/RoutedTabs.jsx | 30 +++++++++++++++- .../screens/Organization/Organization.jsx | 36 ++++++++++--------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/app.scss b/src/app.scss index 05fef0b208..b15ebc0a61 100644 --- a/src/app.scss +++ b/src/app.scss @@ -203,15 +203,6 @@ text-align: right; } -.awx-orgTabs-container{ - display: flex -} - -.awx-orgTabs__bottom-border{ - flex-grow: 1; - border-bottom: 1px solid #d2d2d2 -} - // // AlertModal styles // diff --git a/src/components/Tabs/RoutedTabs.jsx b/src/components/Tabs/RoutedTabs.jsx index 4100e766b6..2e2b5b4991 100644 --- a/src/components/Tabs/RoutedTabs.jsx +++ b/src/components/Tabs/RoutedTabs.jsx @@ -1,7 +1,35 @@ import React from 'react'; import { shape, string, number, arrayOf } from 'prop-types'; -import { Tab, Tabs } from '@patternfly/react-core'; +import { Tab, Tabs as PFTabs } from '@patternfly/react-core'; import { withRouter } from 'react-router-dom'; +import styled from 'styled-components'; + +const Tabs = styled(PFTabs)` + --pf-c-tabs__button--PaddingLeft: 20px; + --pf-c-tabs__button--PaddingRight: 20px; + + .pf-c-tabs__list { + li:first-of-type .pf-c-tabs__button { + &::before { + border-left: none; + } + &::after { + margin-left: 0; + } + } + } + + &::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + border: solid var(--pf-c-tabs__item--BorderColor); + border-width: var(--pf-c-tabs__item--BorderWidth) 0 var(--pf-c-tabs__item--BorderWidth) 0; + } +`; function RoutedTabs (props) { const { history, tabsArray } = props; diff --git a/src/pages/Organizations/screens/Organization/Organization.jsx b/src/pages/Organizations/screens/Organization/Organization.jsx index 71100980f7..0c043dffbd 100644 --- a/src/pages/Organizations/screens/Organization/Organization.jsx +++ b/src/pages/Organizations/screens/Organization/Organization.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { Switch, Route, withRouter, Redirect } from 'react-router-dom'; -import { Card, CardHeader, PageSection } from '@patternfly/react-core'; +import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core'; import CardCloseButton from '../../../../components/CardCloseButton'; import ContentError from '../../../../components/ContentError'; import OrganizationAccess from './OrganizationAccess'; @@ -12,6 +12,7 @@ import OrganizationNotifications from './OrganizationNotifications'; import OrganizationTeams from './OrganizationTeams'; import RoutedTabs from '../../../../components/Tabs/RoutedTabs'; import { OrganizationsAPI } from '../../../../api'; +import styled from 'styled-components'; class Organization extends Component { constructor (props) { @@ -130,23 +131,24 @@ class Organization extends Component { }); } + const CardHeader = styled(PFCardHeader)` + --pf-c-card--first-child--PaddingTop: 0; + --pf-c-card--child--PaddingLeft: 0; + --pf-c-card--child--PaddingRight: 0; + position: relative; + `; + let cardHeader = ( - - -
- - -
-
- - + loading ? '' : ( + + + + + ) ); if (!isInitialized) { From 54f9dd5e9830002c9bf0b09317b7ad64cd798b1f Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 12 Jun 2019 16:52:42 -0400 Subject: [PATCH 2/6] Add Job API model --- src/api/index.js | 3 +++ src/api/models/Jobs.js | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/api/models/Jobs.js diff --git a/src/api/index.js b/src/api/index.js index 018ae790d4..418b150b04 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,5 +1,6 @@ import Config from './models/Config'; import InstanceGroups from './models/InstanceGroups'; +import Jobs from './models/Jobs'; import Me from './models/Me'; import Organizations from './models/Organizations'; import Root from './models/Root'; @@ -9,6 +10,7 @@ import Users from './models/Users'; const ConfigAPI = new Config(); const InstanceGroupsAPI = new InstanceGroups(); +const JobsAPI = new Jobs(); const MeAPI = new Me(); const OrganizationsAPI = new Organizations(); const RootAPI = new Root(); @@ -19,6 +21,7 @@ const UsersAPI = new Users(); export { ConfigAPI, InstanceGroupsAPI, + JobsAPI, MeAPI, OrganizationsAPI, RootAPI, diff --git a/src/api/models/Jobs.js b/src/api/models/Jobs.js new file mode 100644 index 0000000000..cea169b72a --- /dev/null +++ b/src/api/models/Jobs.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Jobs extends Base { + constructor (http) { + super(http); + this.baseUrl = '/api/v2/jobs/'; + } +} + +export default Jobs; From 508d8311dd3c77b3c1a22432b0e1245ba49c754a Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 12 Jun 2019 16:53:37 -0400 Subject: [PATCH 3/6] Add Job results skeleton --- src/pages/Jobs.jsx | 28 ----- src/pages/Jobs/Job.jsx | 139 +++++++++++++++++++++++++ src/pages/Jobs/JobDetail/JobDetail.jsx | 23 ++++ src/pages/Jobs/JobDetail/index.js | 4 + src/pages/Jobs/JobOutput/JobOutput.jsx | 23 ++++ src/pages/Jobs/JobOutput/index.js | 4 + src/pages/Jobs/Jobs.jsx | 85 +++++++++++++++ src/pages/Jobs/index.js | 3 + 8 files changed, 281 insertions(+), 28 deletions(-) delete mode 100644 src/pages/Jobs.jsx create mode 100644 src/pages/Jobs/Job.jsx create mode 100644 src/pages/Jobs/JobDetail/JobDetail.jsx create mode 100644 src/pages/Jobs/JobDetail/index.js create mode 100644 src/pages/Jobs/JobOutput/JobOutput.jsx create mode 100644 src/pages/Jobs/JobOutput/index.js create mode 100644 src/pages/Jobs/Jobs.jsx create mode 100644 src/pages/Jobs/index.js diff --git a/src/pages/Jobs.jsx b/src/pages/Jobs.jsx deleted file mode 100644 index e78e62cfbd..0000000000 --- a/src/pages/Jobs.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { - PageSection, - PageSectionVariants, - Title, -} from '@patternfly/react-core'; - -class Jobs extends Component { - render () { - const { i18n } = this.props; - const { light, medium } = PageSectionVariants; - - return ( - - - - {i18n._(t`Jobs`)} - - - - - ); - } -} - -export default withI18n()(Jobs); diff --git a/src/pages/Jobs/Job.jsx b/src/pages/Jobs/Job.jsx new file mode 100644 index 0000000000..bdf5746a51 --- /dev/null +++ b/src/pages/Jobs/Job.jsx @@ -0,0 +1,139 @@ +import React, { Component } from 'react' +import { Route, withRouter, Switch, Redirect } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import styled from 'styled-components'; + +import { withNetwork } from '../../contexts/Network'; +import { JobsAPI } from '../../api'; +import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core'; +import CardCloseButton from '../../components/CardCloseButton'; +import RoutedTabs from '../../components/Tabs/RoutedTabs'; +import JobDetail from './JobDetail'; +import JobOutput from './JobOutput'; + +export class Job extends Component { + constructor (props) { + super(props); + + this.state = { + job: null, + error: false, + loading: true + } + + this.fetchJob = this.fetchJob.bind(this); + } + + componentDidMount () { + this.fetchJob(); + } + + async componentDidUpdate (prevProps) { + const { location } = this.props; + if (location !== prevProps.location) { + await this.fetchJob(); + } + } + async fetchJob () { + const { + match, + setBreadcrumb, + handleHttpError + } = this.props; + + try { + const { data } = await JobsAPI.readDetail(match.params.id); + setBreadcrumb(data); + this.setState({ + job: data, + loading: false + }) + } catch (error) { + handleHttpError(error) || this.setState({ error: true, loading: false }); + } + } + + render() { + const { + location, + history, + match, + i18n + } = this.props; + + const { + job, + error, + loading + } = this.state; + + const tabsArray = [ + { name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 }, + { name: i18n._(t`Output`), link: `${match.url}/output`, id: 1 } + ] + + const CardHeader = styled(PFCardHeader)` + --pf-c-card--first-child--PaddingTop: 0; + --pf-c-card--child--PaddingLeft: 0; + --pf-c-card--child--PaddingRight: 0; + position: relative; + `; + + const cardHeader = ( + loading ? '' : ( + + + + + ) + ); + + return ( + + + { cardHeader } + + + {job && ( + ( + + )} + /> + )} + {job && ( + ( + + )} + /> + )} + + + + ) + } +} + +export default withI18n()(withNetwork(withRouter(Job))); diff --git a/src/pages/Jobs/JobDetail/JobDetail.jsx b/src/pages/Jobs/JobDetail/JobDetail.jsx new file mode 100644 index 0000000000..ff1fa26a95 --- /dev/null +++ b/src/pages/Jobs/JobDetail/JobDetail.jsx @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import { CardBody } from '@patternfly/react-core'; + + +class JobDetail extends Component { + constructor (props) { + super(props); + } + + render () { + const { + job + } = this.props; + + return ( + + {job.name} + + ); + } +} + +export default JobDetail; diff --git a/src/pages/Jobs/JobDetail/index.js b/src/pages/Jobs/JobDetail/index.js new file mode 100644 index 0000000000..a13fb1cebc --- /dev/null +++ b/src/pages/Jobs/JobDetail/index.js @@ -0,0 +1,4 @@ +import JobDetail from './JobDetail'; + +export default JobDetail; + diff --git a/src/pages/Jobs/JobOutput/JobOutput.jsx b/src/pages/Jobs/JobOutput/JobOutput.jsx new file mode 100644 index 0000000000..b5087d0d51 --- /dev/null +++ b/src/pages/Jobs/JobOutput/JobOutput.jsx @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import { CardBody } from '@patternfly/react-core'; + + +class JobOutput extends Component { + constructor (props) { + super(props); + } + + render () { + const { + job + } = this.props; + + return ( + + {job.name} + + ); + } +} + +export default JobOutput; diff --git a/src/pages/Jobs/JobOutput/index.js b/src/pages/Jobs/JobOutput/index.js new file mode 100644 index 0000000000..8d482ce336 --- /dev/null +++ b/src/pages/Jobs/JobOutput/index.js @@ -0,0 +1,4 @@ +import JobOutput from './JobOutput'; + +export default JobOutput; + diff --git a/src/pages/Jobs/Jobs.jsx b/src/pages/Jobs/Jobs.jsx new file mode 100644 index 0000000000..aabb48d8c0 --- /dev/null +++ b/src/pages/Jobs/Jobs.jsx @@ -0,0 +1,85 @@ +import React, { Component, Fragment } from 'react'; +import { Route, withRouter, Switch } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; + +import { NetworkProvider } from '../../contexts/Network'; +import { withRootDialog } from '../../contexts/RootDialog'; + +import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; +import Job from './Job'; + +class Jobs extends Component { + constructor (props) { + super(props); + + const { i18n } = props; + + this.state = { + breadcrumbConfig: { + '/jobs': i18n._(`Jobs`) + } + } + } + + setBreadcrumbConfig = (job) => { + const { i18n } = this.props; + + if (!job) { + return; + } + + const breadcrumbConfig = { + '/jobs': i18n._(`Jobs`), + [`/jobs/${job.id}`]: `${job.name}`, + [`/jobs/${job.id}/details`]: i18n._(`Details`), + [`/jobs/${job.id}/output`]: i18n._(`Output`) + }; + + this.setState({ breadcrumbConfig }); + } + + render () { + const { match, history, location, setRootDialogMessage, i18n } = this.props; + const { breadcrumbConfig } = this.state; + + return ( + + + + ( + { + history.replace('/jobs'); + setRootDialogMessage({ + title: '404', + bodyText: ( + + {i18n._(t`Cannot find job with ID`)} + {` ${newRouteMatch.params.id}`} + . + + ), + variant: 'warning' + }); + }} + > + + + )} + /> + + + ); + } +} + +export default withI18n()(withRootDialog(withRouter(Jobs))); diff --git a/src/pages/Jobs/index.js b/src/pages/Jobs/index.js new file mode 100644 index 0000000000..d640a4f4d9 --- /dev/null +++ b/src/pages/Jobs/index.js @@ -0,0 +1,3 @@ +import Jobs from "./Jobs"; + +export default Jobs; \ No newline at end of file From cda5cc25b88b8c957cbd76f16159311324797b87 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 12 Jun 2019 16:54:01 -0400 Subject: [PATCH 4/6] Add Job results skeleton tests --- __tests__/pages/Jobs.test.jsx | 29 --------------- __tests__/pages/Jobs/Jobs.test.jsx | 36 +++++++++++++++++++ __tests__/pages/Jobs/screens/Job/Job.test.jsx | 10 ++++++ .../pages/Jobs/screens/Job/JobDetail.test.jsx | 15 ++++++++ .../pages/Jobs/screens/Job/JobOutput.test.jsx | 15 ++++++++ src/pages/Jobs/Job.jsx | 1 - src/pages/Jobs/Jobs.jsx | 2 +- 7 files changed, 77 insertions(+), 31 deletions(-) delete mode 100644 __tests__/pages/Jobs.test.jsx create mode 100644 __tests__/pages/Jobs/Jobs.test.jsx create mode 100644 __tests__/pages/Jobs/screens/Job/Job.test.jsx create mode 100644 __tests__/pages/Jobs/screens/Job/JobDetail.test.jsx create mode 100644 __tests__/pages/Jobs/screens/Job/JobOutput.test.jsx diff --git a/__tests__/pages/Jobs.test.jsx b/__tests__/pages/Jobs.test.jsx deleted file mode 100644 index 82fbe1867e..0000000000 --- a/__tests__/pages/Jobs.test.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { mountWithContexts } from '../enzymeHelpers'; -import Jobs from '../../src/pages/Jobs'; - -describe('', () => { - let pageWrapper; - let pageSections; - let title; - - beforeEach(() => { - pageWrapper = mountWithContexts(); - pageSections = pageWrapper.find('PageSection'); - title = pageWrapper.find('Title'); - }); - - afterEach(() => { - pageWrapper.unmount(); - }); - - test('initially renders without crashing', () => { - expect(pageWrapper.length).toBe(1); - expect(pageSections.length).toBe(2); - expect(title.length).toBe(1); - expect(title.props().size).toBe('2xl'); - pageSections.forEach(section => { - expect(section.props().variant).toBeDefined(); - }); - }); -}); diff --git a/__tests__/pages/Jobs/Jobs.test.jsx b/__tests__/pages/Jobs/Jobs.test.jsx new file mode 100644 index 0000000000..5fd09bbc57 --- /dev/null +++ b/__tests__/pages/Jobs/Jobs.test.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { createMemoryHistory } from 'history'; +import { mountWithContexts } from '../../enzymeHelpers'; +import Jobs from '../../../src/pages/Jobs'; + +describe('', () => { + test('initially renders succesfully', () => { + mountWithContexts( + + ); + }); + + test('should display correct breadcrumb heading', () => { + const history = createMemoryHistory({ + initialEntries: ['/jobs'], + }); + const match = { path: '/jobs', url: '/jobs', isExact: true }; + + const wrapper = mountWithContexts( + , + { + context: { + router: { + history, + route: { + location: history.location, + match + } + } + } + } + ); + expect(wrapper.find('BreadcrumbHeading').text()).toEqual('Jobs'); + wrapper.unmount(); + }); +}); diff --git a/__tests__/pages/Jobs/screens/Job/Job.test.jsx b/__tests__/pages/Jobs/screens/Job/Job.test.jsx new file mode 100644 index 0000000000..e5ef908692 --- /dev/null +++ b/__tests__/pages/Jobs/screens/Job/Job.test.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { mountWithContexts } from '../../../../enzymeHelpers'; +import Job from '../../../../../src/pages/Jobs/Job'; + + +describe('', () => { + test('initially renders succesfully', () => { + mountWithContexts(); + }); +}); diff --git a/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx new file mode 100644 index 0000000000..e490999c50 --- /dev/null +++ b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { mountWithContexts } from '../../../../enzymeHelpers'; +import JobDetail from '../../../../../src/pages/Jobs/JobDetail/'; + +describe('', () => { + const mockDetails = { + name: 'Foo' + }; + + test('initially renders succesfully', () => { + mountWithContexts( + + ); + }); +}); diff --git a/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx b/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx new file mode 100644 index 0000000000..c13f2d9256 --- /dev/null +++ b/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { mountWithContexts } from '../../../../enzymeHelpers'; +import JobOutput from '../../../../../src/pages/Jobs/JobOutput/'; + +describe('', () => { + const mockDetails = { + name: 'Foo' + }; + + test('initially renders succesfully', () => { + mountWithContexts( + + ); + }); +}); diff --git a/src/pages/Jobs/Job.jsx b/src/pages/Jobs/Job.jsx index bdf5746a51..b5ebf66d17 100644 --- a/src/pages/Jobs/Job.jsx +++ b/src/pages/Jobs/Job.jsx @@ -64,7 +64,6 @@ export class Job extends Component { const { job, - error, loading } = this.state; diff --git a/src/pages/Jobs/Jobs.jsx b/src/pages/Jobs/Jobs.jsx index aabb48d8c0..48bbaf80f6 100644 --- a/src/pages/Jobs/Jobs.jsx +++ b/src/pages/Jobs/Jobs.jsx @@ -17,7 +17,7 @@ class Jobs extends Component { this.state = { breadcrumbConfig: { - '/jobs': i18n._(`Jobs`) + '/jobs': i18n._(t`Jobs`) } } } From 416d30a189fe923a9b7741bb38ab9676086152ef Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Thu, 13 Jun 2019 13:47:48 -0400 Subject: [PATCH 5/6] Add close button to job detail and test --- __tests__/pages/Jobs/Jobs.test.jsx | 4 +-- .../pages/Jobs/screens/Job/JobDetail.test.jsx | 9 ++++++ src/pages/Jobs/JobDetail/JobDetail.jsx | 28 ++++++++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/__tests__/pages/Jobs/Jobs.test.jsx b/__tests__/pages/Jobs/Jobs.test.jsx index 5fd09bbc57..8e25ce6220 100644 --- a/__tests__/pages/Jobs/Jobs.test.jsx +++ b/__tests__/pages/Jobs/Jobs.test.jsx @@ -10,7 +10,7 @@ describe('', () => { ); }); - test('should display correct breadcrumb heading', () => { + test('should display a breadcrumb heading', () => { const history = createMemoryHistory({ initialEntries: ['/jobs'], }); @@ -30,7 +30,7 @@ describe('', () => { } } ); - expect(wrapper.find('BreadcrumbHeading').text()).toEqual('Jobs'); + expect(wrapper.find('BreadcrumbHeading').length).toBe(1); wrapper.unmount(); }); }); diff --git a/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx index e490999c50..de475516ad 100644 --- a/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx +++ b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx @@ -12,4 +12,13 @@ describe('', () => { ); }); + + test('should display a Close button', () => { + const wrapper = mountWithContexts( + + ); + + expect(wrapper.find('Button[aria-label="close"]').length).toBe(1); + wrapper.unmount(); + }); }); diff --git a/src/pages/Jobs/JobDetail/JobDetail.jsx b/src/pages/Jobs/JobDetail/JobDetail.jsx index ff1fa26a95..eab33c9f89 100644 --- a/src/pages/Jobs/JobDetail/JobDetail.jsx +++ b/src/pages/Jobs/JobDetail/JobDetail.jsx @@ -1,7 +1,14 @@ import React, { Component } from 'react'; -import { CardBody } from '@patternfly/react-core'; - +import { Link, withRouter } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { CardBody, Button } from '@patternfly/react-core'; +import styled from 'styled-components'; +const ActionButtonWrapper = styled.div` + display: flex; + justify-content: flex-end; +`; class JobDetail extends Component { constructor (props) { super(props); @@ -9,15 +16,28 @@ class JobDetail extends Component { render () { const { - job + job, + i18n } = this.props; + return ( {job.name} + + + + ); } } -export default JobDetail; +export default withI18n()(withRouter(JobDetail)); From 096f5fb3244be15e9f3e5ba4f5cbe41eefdd0f26 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Thu, 13 Jun 2019 16:48:12 -0400 Subject: [PATCH 6/6] Fix lint errors and pull in new content error and loading handler --- __tests__/pages/Jobs/screens/Job/Job.test.jsx | 3 +- .../pages/Jobs/screens/Job/JobDetail.test.jsx | 6 +- .../pages/Jobs/screens/Job/JobOutput.test.jsx | 4 +- src/pages/Jobs/Job.jsx | 89 +++++++++++-------- src/pages/Jobs/JobDetail/JobDetail.jsx | 9 +- src/pages/Jobs/JobOutput/JobOutput.jsx | 7 +- src/pages/Jobs/Jobs.jsx | 47 +++------- src/pages/Jobs/index.js | 5 +- .../screens/Organization/Organization.jsx | 20 ++--- 9 files changed, 85 insertions(+), 105 deletions(-) diff --git a/__tests__/pages/Jobs/screens/Job/Job.test.jsx b/__tests__/pages/Jobs/screens/Job/Job.test.jsx index e5ef908692..780fe26ad9 100644 --- a/__tests__/pages/Jobs/screens/Job/Job.test.jsx +++ b/__tests__/pages/Jobs/screens/Job/Job.test.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { mountWithContexts } from '../../../../enzymeHelpers'; -import Job from '../../../../../src/pages/Jobs/Job'; - +import { Job } from '../../../../../src/pages/Jobs'; describe('', () => { test('initially renders succesfully', () => { diff --git a/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx index de475516ad..6f5e76e71f 100644 --- a/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx +++ b/__tests__/pages/Jobs/screens/Job/JobDetail.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { mountWithContexts } from '../../../../enzymeHelpers'; -import JobDetail from '../../../../../src/pages/Jobs/JobDetail/'; +import JobDetail from '../../../../../src/pages/Jobs/JobDetail'; describe('', () => { const mockDetails = { @@ -9,13 +9,13 @@ describe('', () => { test('initially renders succesfully', () => { mountWithContexts( - + ); }); test('should display a Close button', () => { const wrapper = mountWithContexts( - + ); expect(wrapper.find('Button[aria-label="close"]').length).toBe(1); diff --git a/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx b/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx index c13f2d9256..e92fa37fc7 100644 --- a/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx +++ b/__tests__/pages/Jobs/screens/Job/JobOutput.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { mountWithContexts } from '../../../../enzymeHelpers'; -import JobOutput from '../../../../../src/pages/Jobs/JobOutput/'; +import JobOutput from '../../../../../src/pages/Jobs/JobOutput'; describe('', () => { const mockDetails = { @@ -9,7 +9,7 @@ describe('', () => { test('initially renders succesfully', () => { mountWithContexts( - + ); }); }); diff --git a/src/pages/Jobs/Job.jsx b/src/pages/Jobs/Job.jsx index b5ebf66d17..69c093fe6e 100644 --- a/src/pages/Jobs/Job.jsx +++ b/src/pages/Jobs/Job.jsx @@ -1,12 +1,11 @@ -import React, { Component } from 'react' +import React, { Component } from 'react'; import { Route, withRouter, Switch, Redirect } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import styled from 'styled-components'; - -import { withNetwork } from '../../contexts/Network'; -import { JobsAPI } from '../../api'; import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core'; +import { JobsAPI } from '../../api'; +import ContentError from '../../components/ContentError'; import CardCloseButton from '../../components/CardCloseButton'; import RoutedTabs from '../../components/Tabs/RoutedTabs'; import JobDetail from './JobDetail'; @@ -18,15 +17,17 @@ export class Job extends Component { this.state = { job: null, - error: false, - loading: true - } + contentError: false, + contentLoading: true, + isInitialized: false + }; this.fetchJob = this.fetchJob.bind(this); } - componentDidMount () { - this.fetchJob(); + async componentDidMount () { + await this.fetchJob(); + this.setState({ isInitialized: true }); } async componentDidUpdate (prevProps) { @@ -35,28 +36,28 @@ export class Job extends Component { await this.fetchJob(); } } + async fetchJob () { const { match, setBreadcrumb, - handleHttpError } = this.props; + const id = parseInt(match.params.id, 10); + this.setState({ contentError: false, contentLoading: true }); try { - const { data } = await JobsAPI.readDetail(match.params.id); + const { data } = await JobsAPI.readDetail(id); setBreadcrumb(data); - this.setState({ - job: data, - loading: false - }) + this.setState({ job: data }); } catch (error) { - handleHttpError(error) || this.setState({ error: true, loading: false }); + this.setState({ contentError: true }); + } finally { + this.setState({ contentLoading: false }); } } - render() { + render () { const { - location, history, match, i18n @@ -64,13 +65,15 @@ export class Job extends Component { const { job, - loading + contentError, + contentLoading, + isInitialized } = this.state; const tabsArray = [ { name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 }, { name: i18n._(t`Output`), link: `${match.url}/output`, id: 1 } - ] + ]; const CardHeader = styled(PFCardHeader)` --pf-c-card--first-child--PaddingTop: 0; @@ -79,19 +82,35 @@ export class Job extends Component { position: relative; `; - const cardHeader = ( - loading ? '' : ( - - - - - ) + let cardHeader = ( + + + + ); + if (!isInitialized) { + cardHeader = null; + } + + if (!match) { + cardHeader = null; + } + + if (!contentLoading && contentError) { + return ( + + + + + + ); + } + return ( @@ -108,8 +127,6 @@ export class Job extends Component { render={() => ( )} @@ -121,8 +138,6 @@ export class Job extends Component { render={() => ( )} @@ -131,8 +146,8 @@ export class Job extends Component { - ) + ); } } -export default withI18n()(withNetwork(withRouter(Job))); +export default withI18n()(withRouter(Job)); diff --git a/src/pages/Jobs/JobDetail/JobDetail.jsx b/src/pages/Jobs/JobDetail/JobDetail.jsx index eab33c9f89..fa6522382c 100644 --- a/src/pages/Jobs/JobDetail/JobDetail.jsx +++ b/src/pages/Jobs/JobDetail/JobDetail.jsx @@ -10,27 +10,22 @@ const ActionButtonWrapper = styled.div` justify-content: flex-end; `; class JobDetail extends Component { - constructor (props) { - super(props); - } - render () { const { job, i18n } = this.props; - return ( {job.name} diff --git a/src/pages/Jobs/JobOutput/JobOutput.jsx b/src/pages/Jobs/JobOutput/JobOutput.jsx index b5087d0d51..dc8ae07947 100644 --- a/src/pages/Jobs/JobOutput/JobOutput.jsx +++ b/src/pages/Jobs/JobOutput/JobOutput.jsx @@ -1,12 +1,7 @@ import React, { Component } from 'react'; -import { CardBody } from '@patternfly/react-core'; - +import { CardBody } from '@patternfly/react-core'; class JobOutput extends Component { - constructor (props) { - super(props); - } - render () { const { job diff --git a/src/pages/Jobs/Jobs.jsx b/src/pages/Jobs/Jobs.jsx index 48bbaf80f6..125d94d770 100644 --- a/src/pages/Jobs/Jobs.jsx +++ b/src/pages/Jobs/Jobs.jsx @@ -2,12 +2,8 @@ import React, { Component, Fragment } from 'react'; import { Route, withRouter, Switch } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; - -import { NetworkProvider } from '../../contexts/Network'; -import { withRootDialog } from '../../contexts/RootDialog'; - import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; -import Job from './Job'; +import { Job } from '.'; class Jobs extends Component { constructor (props) { @@ -19,7 +15,7 @@ class Jobs extends Component { breadcrumbConfig: { '/jobs': i18n._(t`Jobs`) } - } + }; } setBreadcrumbConfig = (job) => { @@ -30,17 +26,17 @@ class Jobs extends Component { } const breadcrumbConfig = { - '/jobs': i18n._(`Jobs`), + '/jobs': i18n._(t`Jobs`), [`/jobs/${job.id}`]: `${job.name}`, - [`/jobs/${job.id}/details`]: i18n._(`Details`), - [`/jobs/${job.id}/output`]: i18n._(`Output`) + [`/jobs/${job.id}/details`]: i18n._(t`Details`), + [`/jobs/${job.id}/output`]: i18n._(t`Output`) }; this.setState({ breadcrumbConfig }); } render () { - const { match, history, location, setRootDialogMessage, i18n } = this.props; + const { match, history, location } = this.props; const { breadcrumbConfig } = this.state; return ( @@ -51,29 +47,12 @@ class Jobs extends Component { ( - { - history.replace('/jobs'); - setRootDialogMessage({ - title: '404', - bodyText: ( - - {i18n._(t`Cannot find job with ID`)} - {` ${newRouteMatch.params.id}`} - . - - ), - variant: 'warning' - }); - }} - > - - + render={() => ( + )} /> @@ -82,4 +61,4 @@ class Jobs extends Component { } } -export default withI18n()(withRootDialog(withRouter(Jobs))); +export default withI18n()(withRouter(Jobs)); diff --git a/src/pages/Jobs/index.js b/src/pages/Jobs/index.js index d640a4f4d9..7a03fd5819 100644 --- a/src/pages/Jobs/index.js +++ b/src/pages/Jobs/index.js @@ -1,3 +1,2 @@ -import Jobs from "./Jobs"; - -export default Jobs; \ No newline at end of file +export { default as Job } from './Job'; +export { default } from './Jobs'; diff --git a/src/pages/Organizations/screens/Organization/Organization.jsx b/src/pages/Organizations/screens/Organization/Organization.jsx index 0c043dffbd..1e0a1a7bc3 100644 --- a/src/pages/Organizations/screens/Organization/Organization.jsx +++ b/src/pages/Organizations/screens/Organization/Organization.jsx @@ -3,6 +3,7 @@ import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { Switch, Route, withRouter, Redirect } from 'react-router-dom'; import { Card, CardHeader as PFCardHeader, PageSection } from '@patternfly/react-core'; +import styled from 'styled-components'; import CardCloseButton from '../../../../components/CardCloseButton'; import ContentError from '../../../../components/ContentError'; import OrganizationAccess from './OrganizationAccess'; @@ -12,7 +13,6 @@ import OrganizationNotifications from './OrganizationNotifications'; import OrganizationTeams from './OrganizationTeams'; import RoutedTabs from '../../../../components/Tabs/RoutedTabs'; import { OrganizationsAPI } from '../../../../api'; -import styled from 'styled-components'; class Organization extends Component { constructor (props) { @@ -139,16 +139,14 @@ class Organization extends Component { `; let cardHeader = ( - loading ? '' : ( - - - - - ) + + + + ); if (!isInitialized) {