Refactor Teams and Team components

This commit is contained in:
Marliana Lara 2020-01-28 12:26:40 -05:00
parent 9cb7b0902a
commit de75592f2a
No known key found for this signature in database
GPG Key ID: 38C73B40DFA809EE
4 changed files with 177 additions and 216 deletions

View File

@ -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 = (
<TabbedCardHeader>
<RoutedTabs tabsArray={tabsArray} />
<CardCloseButton linkTo="/teams" />
</TabbedCardHeader>
);
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 = (
<TabbedCardHeader>
<RoutedTabs tabsArray={tabsArray} />
<CardCloseButton linkTo="/teams" />
</TabbedCardHeader>
);
if (!isInitialized) {
cardHeader = null;
}
if (location.pathname.endsWith('edit')) {
cardHeader = null;
}
if (!hasContentLoading && contentError) {
return (
<PageSection>
<Card className="awx-c-card">
<ContentError error={contentError}>
{contentError.response.status === 404 && (
<span>
{i18n._(`Team not found.`)}{' '}
<Link to="/teams">{i18n._(`View all Teams.`)}</Link>
</span>
)}
</ContentError>
</Card>
</PageSection>
);
}
if (!hasContentLoading && contentError) {
return (
<PageSection>
<Card className="awx-c-card">
{cardHeader}
<Switch>
<Redirect from="/teams/:id" to="/teams/:id/details" exact />
{team && (
<Route
path="/teams/:id/edit"
render={() => <TeamEdit team={team} />}
/>
<ContentError error={contentError}>
{contentError.response.status === 404 && (
<span>
{i18n._(`Team not found.`)}{' '}
<Link to="/teams">{i18n._(`View all Teams.`)}</Link>
</span>
)}
{team && (
<Route
path="/teams/:id/details"
render={() => <TeamDetail team={team} />}
/>
)}
{team && (
<Route
path="/teams/:id/users"
render={() => <span>Coming soon :)</span>}
/>
)}
{team && (
<Route
path="/teams/:id/access"
render={() => <span>Coming soon :)</span>}
/>
)}
<Route
key="not-found"
path="*"
render={() =>
!hasContentLoading && (
<ContentError isNotFound>
{match.params.id && (
<Link to={`/teams/${match.params.id}/details`}>
{i18n._(`View Team Details`)}
</Link>
)}
</ContentError>
)
}
/>
,
</Switch>
</ContentError>
</Card>
</PageSection>
);
}
return (
<PageSection>
<Card className="awx-c-card">
{cardHeader}
<Switch>
<Redirect from="/teams/:id" to="/teams/:id/details" exact />
{team && (
<Route path="/teams/:id/details">
<TeamDetail team={team} />
</Route>
)}
{team && (
<Route
path="/teams/:id/edit"
render={() => <TeamEdit team={team} />}
/>
)}
{team && (
<Route
path="/teams/:id/users"
render={() => <span>Coming soon :)</span>}
/>
)}
{team && (
<Route
path="/teams/:id/access"
render={() => <span>Coming soon :)</span>}
/>
)}
<Route
key="not-found"
path="*"
render={() =>
!hasContentLoading && (
<ContentError isNotFound>
{id && (
<Link to={`/teams/${id}/details`}>
{i18n._(`View Team Details`)}
</Link>
)}
</ContentError>
)
}
/>
</Switch>
</Card>
</PageSection>
);
}
export default withI18n()(withRouter(Team));
export default withI18n()(Team);
export { Team as _Team };

View File

@ -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('<Team />', () => {
test('initially renders succesfully', () => {
let wrapper;
beforeEach(() => {
TeamsAPI.readDetail.mockResolvedValue({ data: mockTeam });
TeamsAPI.read.mockImplementation(getTeams);
mountWithContexts(<Team setBreadcrumb={() => {}} me={mockMe} />);
});
test('initially renders succesfully', async () => {
await act(async () => {
wrapper = mountWithContexts(
<Team setBreadcrumb={() => {}} 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(
<Team setBreadcrumb={() => {}} 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(
<Team setBreadcrumb={() => {}} 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);
});
});

View File

@ -1,2 +1,2 @@
export { default as TeamList } from './TeamList';
export { default } from './TeamList';
export { default as TeamListItem } from './TeamListItem';

View File

@ -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 (
<Fragment>
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
<Switch>
<Route path={`${match.path}/add`} render={() => <TeamAdd />} />
<Route
path={`${match.path}/:id`}
render={() => (
<Config>
{({ me }) => (
<Team
history={history}
location={location}
setBreadcrumb={this.setBreadcrumbConfig}
me={me || {}}
/>
)}
</Config>
)}
/>
<Route path={`${match.path}`} render={() => <TeamsList />} />
</Switch>
</Fragment>
);
}
return (
<>
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
<Switch>
<Route path="/teams/add">
<TeamAdd />
</Route>
<Route path="/teams/:id">
<Team setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/teams">
<Config>
{({ me }) => <TeamList path="/teams" me={me || {}} />}
</Config>
</Route>
</Switch>
</>
);
}
export { Teams as _Teams };
export default withI18n()(withRouter(Teams));
export default withI18n()(Teams);