From 9cb7b0902a2581668c4e2e4a34c65530817a0b99 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 28 Jan 2020 12:26:21 -0500 Subject: [PATCH 1/2] Fix org team link url --- .../OrganizationTeams/OrganizationTeams.jsx | 4 +++- .../OrganizationTeams.test.jsx | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.jsx b/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.jsx index a8a7873d6b..a94215dcd0 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.jsx @@ -30,7 +30,9 @@ function OrganizationTeams({ id, i18n }) { data: { count = 0, results = [] }, } = await OrganizationsAPI.readTeams(id, params); setItemCount(count); - setTeams(results); + setTeams( + results.map(team => ({ ...team, url: `/teams/${team.id}/details` })) + ); } catch (error) { setContentError(error); } finally { diff --git a/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.test.jsx b/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.test.jsx index 22e9b988a3..fbc5b48a59 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.test.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationTeams/OrganizationTeams.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { OrganizationsAPI } from '@api'; -import { mountWithContexts } from '@testUtils/enzymeHelpers'; +import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { sleep } from '@testUtils/testUtils'; import OrganizationTeams from './OrganizationTeams'; @@ -65,7 +65,9 @@ describe('', () => { wrapper.update(); const list = wrapper.find('PaginatedDataList'); - expect(list.prop('items')).toEqual(listData.data.results); + list.find('DataListCell').forEach((el, index) => { + expect(el.text()).toBe(listData.data.results[index].name); + }); expect(list.prop('itemCount')).toEqual(listData.data.count); expect(list.prop('qsConfig')).toEqual({ namespace: 'team', @@ -78,4 +80,15 @@ describe('', () => { integerFields: ['page', 'page_size'], }); }); + + test('should show content error for failed instance group fetch', async () => { + OrganizationsAPI.readTeams.mockImplementationOnce(() => + Promise.reject(new Error()) + ); + let wrapper; + await act(async () => { + wrapper = mountWithContexts(); + }); + await waitForElement(wrapper, 'ContentError', el => el.length === 1); + }); }); From de75592f2ab558e70d5bda657687929307d99017 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 28 Jan 2020 12:26:40 -0500 Subject: [PATCH 2/2] Refactor Teams and Team components --- awx/ui_next/src/screens/Team/Team.jsx | 235 ++++++++---------- awx/ui_next/src/screens/Team/Team.test.jsx | 47 ++-- .../src/screens/Team/TeamList/index.js | 2 +- awx/ui_next/src/screens/Team/Teams.jsx | 109 ++++---- 4 files changed, 177 insertions(+), 216 deletions(-) diff --git a/awx/ui_next/src/screens/Team/Team.jsx b/awx/ui_next/src/screens/Team/Team.jsx index aa726bef18..d0a385017e 100644 --- a/awx/ui_next/src/screens/Team/Team.jsx +++ b/awx/ui_next/src/screens/Team/Team.jsx @@ -1,7 +1,14 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom'; +import { + Link, + Redirect, + Route, + Switch, + useLocation, + useParams, +} from 'react-router-dom'; import { Card, PageSection } from '@patternfly/react-core'; import CardCloseButton from '@components/CardCloseButton'; import { TabbedCardHeader } from '@components/Card'; @@ -11,148 +18,110 @@ import TeamDetail from './TeamDetail'; import TeamEdit from './TeamEdit'; import { TeamsAPI } from '@api'; -class Team extends Component { - constructor(props) { - super(props); +function Team({ i18n, setBreadcrumb }) { + const [team, setTeam] = useState(null); + const [contentError, setContentError] = useState(null); + const [hasContentLoading, setHasContentLoading] = useState(true); + const location = useLocation(); + const { id } = useParams(); - this.state = { - team: null, - hasContentLoading: true, - contentError: null, - isInitialized: false, - }; - this.loadTeam = this.loadTeam.bind(this); + useEffect(() => { + (async () => { + try { + const { data } = await TeamsAPI.readDetail(id); + setBreadcrumb(data); + setTeam(data); + } catch (error) { + setContentError(error); + } finally { + setHasContentLoading(false); + } + })(); + }, [id, setBreadcrumb]); + + const tabsArray = [ + { name: i18n._(t`Details`), link: `/teams/${id}/details`, id: 0 }, + { name: i18n._(t`Users`), link: `/teams/${id}/users`, id: 1 }, + { name: i18n._(t`Access`), link: `/teams/${id}/access`, id: 2 }, + ]; + + let cardHeader = ( + + + + + ); + + if (location.pathname.endsWith('edit')) { + cardHeader = null; } - async componentDidMount() { - await this.loadTeam(); - this.setState({ isInitialized: true }); - } - - async componentDidUpdate(prevProps) { - const { location, match } = this.props; - const url = `/teams/${match.params.id}/`; - - if ( - prevProps.location.pathname.startsWith(url) && - prevProps.location !== location && - location.pathname === `${url}details` - ) { - await this.loadTeam(); - } - } - - async loadTeam() { - const { match, setBreadcrumb } = this.props; - const id = parseInt(match.params.id, 10); - - this.setState({ contentError: null, hasContentLoading: true }); - try { - const { data } = await TeamsAPI.readDetail(id); - setBreadcrumb(data); - this.setState({ team: data }); - } catch (err) { - this.setState({ contentError: err }); - } finally { - this.setState({ hasContentLoading: false }); - } - } - - render() { - const { location, match, i18n } = this.props; - - const { team, contentError, hasContentLoading, isInitialized } = this.state; - - const tabsArray = [ - { name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 }, - { name: i18n._(t`Users`), link: `${match.url}/users`, id: 1 }, - { name: i18n._(t`Access`), link: `${match.url}/access`, id: 2 }, - ]; - - let cardHeader = ( - - - - - ); - - if (!isInitialized) { - cardHeader = null; - } - - if (location.pathname.endsWith('edit')) { - cardHeader = null; - } - - if (!hasContentLoading && contentError) { - return ( - - - - {contentError.response.status === 404 && ( - - {i18n._(`Team not found.`)}{' '} - {i18n._(`View all Teams.`)} - - )} - - - - ); - } - + if (!hasContentLoading && contentError) { return ( - {cardHeader} - - - {team && ( - } - /> + + {contentError.response.status === 404 && ( + + {i18n._(`Team not found.`)}{' '} + {i18n._(`View all Teams.`)} + )} - {team && ( - } - /> - )} - {team && ( - Coming soon :)} - /> - )} - {team && ( - Coming soon :)} - /> - )} - - !hasContentLoading && ( - - {match.params.id && ( - - {i18n._(`View Team Details`)} - - )} - - ) - } - /> - , - + ); } + + return ( + + + {cardHeader} + + + {team && ( + + + + )} + {team && ( + } + /> + )} + {team && ( + Coming soon :)} + /> + )} + {team && ( + Coming soon :)} + /> + )} + + !hasContentLoading && ( + + {id && ( + + {i18n._(`View Team Details`)} + + )} + + ) + } + /> + + + + ); } -export default withI18n()(withRouter(Team)); +export default withI18n()(Team); export { Team as _Team }; diff --git a/awx/ui_next/src/screens/Team/Team.test.jsx b/awx/ui_next/src/screens/Team/Team.test.jsx index f6b8ac5b04..946b7e77f5 100644 --- a/awx/ui_next/src/screens/Team/Team.test.jsx +++ b/awx/ui_next/src/screens/Team/Team.test.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { createMemoryHistory } from 'history'; import { TeamsAPI } from '@api'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; @@ -34,34 +35,46 @@ async function getTeams() { } describe('', () => { - test('initially renders succesfully', () => { + let wrapper; + + beforeEach(() => { TeamsAPI.readDetail.mockResolvedValue({ data: mockTeam }); TeamsAPI.read.mockImplementation(getTeams); - mountWithContexts( {}} me={mockMe} />); + }); + + test('initially renders succesfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + {}} me={mockMe} /> + ); + }); + expect(wrapper.find('Team').length).toBe(1); }); test('should show content error when user attempts to navigate to erroneous route', async () => { const history = createMemoryHistory({ initialEntries: ['/teams/1/foobar'], }); - const wrapper = mountWithContexts( - {}} me={mockMe} />, - { - context: { - router: { - history, - route: { - location: history.location, - match: { - params: { id: 1 }, - url: '/teams/1/foobar', - path: '/teams/1/foobar', + await act(async () => { + wrapper = mountWithContexts( + {}} me={mockMe} />, + { + context: { + router: { + history, + route: { + location: history.location, + match: { + params: { id: 1 }, + url: '/teams/1/foobar', + path: '/teams/1/foobar', + }, }, }, }, - }, - } - ); + } + ); + }); await waitForElement(wrapper, 'ContentError', el => el.length === 1); }); }); diff --git a/awx/ui_next/src/screens/Team/TeamList/index.js b/awx/ui_next/src/screens/Team/TeamList/index.js index 7f52a34617..83c3d034bd 100644 --- a/awx/ui_next/src/screens/Team/TeamList/index.js +++ b/awx/ui_next/src/screens/Team/TeamList/index.js @@ -1,2 +1,2 @@ -export { default as TeamList } from './TeamList'; +export { default } from './TeamList'; export { default as TeamListItem } from './TeamListItem'; diff --git a/awx/ui_next/src/screens/Team/Teams.jsx b/awx/ui_next/src/screens/Team/Teams.jsx index 8634a058ae..73368c85a2 100644 --- a/awx/ui_next/src/screens/Team/Teams.jsx +++ b/awx/ui_next/src/screens/Team/Teams.jsx @@ -1,79 +1,58 @@ -import React, { Component, Fragment } from 'react'; -import { Route, withRouter, Switch } from 'react-router-dom'; +import React, { useState, useCallback } from 'react'; +import { Route, Switch } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { Config } from '@contexts/Config'; -import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs'; - -import TeamsList from './TeamList/TeamList'; -import TeamAdd from './TeamAdd/TeamAdd'; +import Breadcrumbs from '@components/Breadcrumbs'; +import TeamList from './TeamList'; +import TeamAdd from './TeamAdd'; import Team from './Team'; -class Teams extends Component { - constructor(props) { - super(props); +function Teams({ i18n }) { + const [breadcrumbConfig, setBreadcrumbConfig] = useState({ + '/teams': i18n._(t`Teams`), + '/teams/add': i18n._(t`Create New Team`), + }); - const { i18n } = props; + const buildBreadcrumbConfig = useCallback( + team => { + if (!team) { + return; + } - this.state = { - breadcrumbConfig: { + setBreadcrumbConfig({ '/teams': i18n._(t`Teams`), '/teams/add': i18n._(t`Create New Team`), - }, - }; - } + [`/teams/${team.id}`]: `${team.name}`, + [`/teams/${team.id}/edit`]: i18n._(t`Edit Details`), + [`/teams/${team.id}/details`]: i18n._(t`Details`), + [`/teams/${team.id}/users`]: i18n._(t`Users`), + [`/teams/${team.id}/access`]: i18n._(t`Access`), + }); + }, + [i18n] + ); - setBreadcrumbConfig = team => { - const { i18n } = this.props; - - if (!team) { - return; - } - - const breadcrumbConfig = { - '/teams': i18n._(t`Teams`), - '/teams/add': i18n._(t`Create New Team`), - [`/teams/${team.id}`]: `${team.name}`, - [`/teams/${team.id}/edit`]: i18n._(t`Edit Details`), - [`/teams/${team.id}/details`]: i18n._(t`Details`), - [`/teams/${team.id}/users`]: i18n._(t`Users`), - [`/teams/${team.id}/access`]: i18n._(t`Access`), - }; - - this.setState({ breadcrumbConfig }); - }; - - render() { - const { match, history, location } = this.props; - const { breadcrumbConfig } = this.state; - - return ( - - - - } /> - ( - - {({ me }) => ( - - )} - - )} - /> - } /> - - - ); - } + return ( + <> + + + + + + + + + + + {({ me }) => } + + + + + ); } export { Teams as _Teams }; -export default withI18n()(withRouter(Teams)); +export default withI18n()(Teams);