Update breadcrumb and fetch new hosts when url changes

This commit is contained in:
Marliana Lara
2019-11-20 15:56:05 -05:00
parent fa144aa98f
commit faa0802d97
4 changed files with 165 additions and 201 deletions

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React, { useEffect, useState } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { Card, CardHeader, PageSection } from '@patternfly/react-core'; import { Card, CardHeader, PageSection } from '@patternfly/react-core';
@@ -16,186 +16,152 @@ import InventorySources from './InventorySources';
import { InventoriesAPI } from '@api'; import { InventoriesAPI } from '@api';
import InventoryEdit from './InventoryEdit'; import InventoryEdit from './InventoryEdit';
class Inventory extends Component { function Inventory({ history, i18n, location, match, setBreadcrumb }) {
constructor(props) { const [contentError, setContentError] = useState(null);
super(props); const [hasContentLoading, setHasContentLoading] = useState(true);
const [inventory, setInventory] = useState(null);
this.state = { useEffect(() => {
contentError: null, async function fetchData() {
hasContentLoading: true, try {
inventory: null, const { data } = await InventoriesAPI.readDetail(match.params.id);
}; setBreadcrumb(data);
this.loadInventory = this.loadInventory.bind(this); setInventory(data);
} } catch (error) {
setContentError(error);
async componentDidMount() { } finally {
await this.loadInventory(); setHasContentLoading(false);
} }
async componentDidUpdate(prevProps) {
const { location, match } = this.props;
const url = `/inventories/inventory/${match.params.id}/`;
if (
prevProps.location.pathname.startsWith(url) &&
prevProps.location !== location &&
location.pathname === `${url}details`
) {
await this.loadInventory();
}
}
async loadInventory() {
const { setBreadcrumb, match } = this.props;
const { id } = match.params;
this.setState({ contentError: null, hasContentLoading: true });
try {
const { data } = await InventoriesAPI.readDetail(id);
setBreadcrumb(data);
this.setState({ inventory: data });
} catch (err) {
this.setState({ contentError: err });
} finally {
this.setState({ hasContentLoading: false });
}
}
render() {
const { history, i18n, location, match } = this.props;
const { contentError, hasContentLoading, inventory } = this.state;
const tabsArray = [
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Access`), link: `${match.url}/access`, id: 1 },
{ name: i18n._(t`Groups`), link: `${match.url}/groups`, id: 2 },
{ name: i18n._(t`Hosts`), link: `${match.url}/hosts`, id: 3 },
{ name: i18n._(t`Sources`), link: `${match.url}/sources`, id: 4 },
{
name: i18n._(t`Completed Jobs`),
link: `${match.url}/completed_jobs`,
id: 5,
},
];
let cardHeader = hasContentLoading ? null : (
<CardHeader style={{ padding: 0 }}>
<RoutedTabs history={history} tabsArray={tabsArray} />
<CardCloseButton linkTo="/inventories" />
</CardHeader>
);
if (
location.pathname.endsWith('edit') ||
location.pathname.endsWith('add')
) {
cardHeader = null;
} }
if (!hasContentLoading && contentError) { fetchData();
return ( }, [match.params.id, setBreadcrumb]);
<PageSection>
<Card className="awx-c-card">
<ContentError error={contentError}>
{contentError.response.status === 404 && (
<span>
{i18n._(`Inventory not found.`)}{' '}
<Link to="/inventories">
{i18n._(`View all Inventories.`)}
</Link>
</span>
)}
</ContentError>
</Card>
</PageSection>
);
}
const tabsArray = [
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Access`), link: `${match.url}/access`, id: 1 },
{ name: i18n._(t`Groups`), link: `${match.url}/groups`, id: 2 },
{ name: i18n._(t`Hosts`), link: `${match.url}/hosts`, id: 3 },
{ name: i18n._(t`Sources`), link: `${match.url}/sources`, id: 4 },
{
name: i18n._(t`Completed Jobs`),
link: `${match.url}/completed_jobs`,
id: 5,
},
];
let cardHeader = hasContentLoading ? null : (
<CardHeader style={{ padding: 0 }}>
<RoutedTabs history={history} tabsArray={tabsArray} />
<CardCloseButton linkTo="/inventories" />
</CardHeader>
);
if (location.pathname.endsWith('edit') || location.pathname.endsWith('add')) {
cardHeader = null;
}
if (!hasContentLoading && contentError) {
return ( return (
<PageSection> <PageSection>
<Card className="awx-c-card"> <Card className="awx-c-card">
{cardHeader} <ContentError error={contentError}>
<Switch> {contentError.response.status === 404 && (
<Redirect <span>
from="/inventories/inventory/:id" {i18n._(`Inventory not found.`)}{' '}
to="/inventories/inventory/:id/details" <Link to="/inventories">{i18n._(`View all Inventories.`)}</Link>
exact </span>
/> )}
{inventory && [ </ContentError>
<Route
key="details"
path="/inventories/inventory/:id/details"
render={() => (
<InventoryDetail
match={match}
hasInventoryLoading={hasContentLoading}
inventory={inventory}
/>
)}
/>,
<Route
key="edit"
path="/inventories/inventory/:id/edit"
render={() => <InventoryEdit inventory={inventory} />}
/>,
<Route
key="host-add"
path="/inventories/inventory/:id/hosts/add"
render={() => <InventoryHostAdd />}
/>,
<Route
key="access"
path="/inventories/inventory/:id/access"
render={() => (
<ResourceAccessList
resource={inventory}
apiModel={InventoriesAPI}
/>
)}
/>,
<Route
key="groups"
path="/inventories/inventory/:id/groups"
render={() => <InventoryGroups inventory={inventory} />}
/>,
<Route
key="hosts"
path="/inventories/inventory/:id/hosts"
render={() => <InventoryHosts inventory={inventory} />}
/>,
<Route
key="sources"
path="/inventories/inventory/:id/sources"
render={() => <InventorySources inventory={inventory} />}
/>,
<Route
key="completed_jobs"
path="/inventories/inventory/:id/completed_jobs"
render={() => <InventoryCompletedJobs inventory={inventory} />}
/>,
<Route
key="not-found"
path="*"
render={() =>
!hasContentLoading && (
<ContentError isNotFound>
{match.params.id && (
<Link
to={`/inventories/inventory/${match.params.id}/details`}
>
{i18n._(`View Inventory Details`)}
</Link>
)}
</ContentError>
)
}
/>,
]}
</Switch>
</Card> </Card>
</PageSection> </PageSection>
); );
} }
return (
<PageSection>
<Card className="awx-c-card">
{cardHeader}
<Switch>
<Redirect
from="/inventories/inventory/:id"
to="/inventories/inventory/:id/details"
exact
/>
{inventory && [
<Route
key="details"
path="/inventories/inventory/:id/details"
render={() => (
<InventoryDetail
match={match}
hasInventoryLoading={hasContentLoading}
inventory={inventory}
/>
)}
/>,
<Route
key="edit"
path="/inventories/inventory/:id/edit"
render={() => <InventoryEdit inventory={inventory} />}
/>,
<Route
key="host-add"
path="/inventories/inventory/:id/hosts/add"
render={() => <InventoryHostAdd />}
/>,
<Route
key="access"
path="/inventories/inventory/:id/access"
render={() => (
<ResourceAccessList
resource={inventory}
apiModel={InventoriesAPI}
/>
)}
/>,
<Route
key="groups"
path="/inventories/inventory/:id/groups"
render={() => <InventoryGroups inventory={inventory} />}
/>,
<Route
key="hosts"
path="/inventories/inventory/:id/hosts"
render={() => <InventoryHosts />}
/>,
<Route
key="sources"
path="/inventories/inventory/:id/sources"
render={() => <InventorySources inventory={inventory} />}
/>,
<Route
key="completed_jobs"
path="/inventories/inventory/:id/completed_jobs"
render={() => <InventoryCompletedJobs inventory={inventory} />}
/>,
<Route
key="not-found"
path="*"
render={() =>
!hasContentLoading && (
<ContentError isNotFound>
{match.params.id && (
<Link
to={`/inventories/inventory/${match.params.id}/details`}
>
{i18n._(`View Inventory Details`)}
</Link>
)}
</ContentError>
)
}
/>,
]}
</Switch>
</Card>
</PageSection>
);
} }
export { Inventory as _Inventory }; export { Inventory as _Inventory };

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { InventoriesAPI } from '@api'; import { InventoriesAPI } from '@api';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
@@ -12,41 +13,38 @@ InventoriesAPI.readDetail.mockResolvedValue({
}); });
describe('<Inventory />', () => { describe('<Inventory />', () => {
test('initially renders succesfully', async done => { let wrapper;
const wrapper = mountWithContexts(
<Inventory setBreadcrumb={() => {}} match={{ params: { id: 1 } }} /> test('initially renders succesfully', async () => {
); await act(async () => {
await waitForElement( wrapper = mountWithContexts(
wrapper, <Inventory setBreadcrumb={() => {}} match={{ params: { id: 1 } }} />
'Inventory', );
el => el.state('hasContentLoading') === true });
); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
await waitForElement(
wrapper,
'Inventory',
el => el.state('hasContentLoading') === false
);
await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 6); await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 6);
done();
}); });
test('should show content error when user attempts to navigate to erroneous route', async () => { test('should show content error when user attempts to navigate to erroneous route', async () => {
const history = createMemoryHistory({ const history = createMemoryHistory({
initialEntries: ['/inventories/inventory/1/foobar'], initialEntries: ['/inventories/inventory/1/foobar'],
}); });
const wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />, { await act(async () => {
context: { wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />, {
router: { context: {
history, router: {
route: { history,
location: history.location, route: {
match: { location: history.location,
params: { id: 1 }, match: {
url: '/inventories/inventory/1/foobar', params: { id: 1 },
path: '/inventories/inventory/1/foobar', url: '/inventories/inventory/1/foobar',
path: '/inventories/inventory/1/foobar',
},
}, },
}, },
}, },
}, });
}); });
await waitForElement(wrapper, 'ContentError', el => el.length === 1); await waitForElement(wrapper, 'ContentError', el => el.length === 1);
}); });

View File

@@ -20,7 +20,7 @@ const QS_CONFIG = getQSConfig('host', {
order_by: 'name', order_by: 'name',
}); });
function InventoryHosts({ i18n, location, match, inventory }) { function InventoryHosts({ i18n, location, match }) {
const [actions, setActions] = useState(null); const [actions, setActions] = useState(null);
const [contentError, setContentError] = useState(null); const [contentError, setContentError] = useState(null);
const [deletionError, setDeletionError] = useState(null); const [deletionError, setDeletionError] = useState(null);
@@ -47,7 +47,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
data: { actions: optionActions }, data: { actions: optionActions },
}, },
] = await Promise.all([ ] = await Promise.all([
fetchHosts(inventory.id, location.search), fetchHosts(match.params.id, location.search),
InventoriesAPI.readOptions(), InventoriesAPI.readOptions(),
]); ]);
@@ -62,7 +62,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
} }
fetchData(); fetchData();
}, [inventory, location]); }, [match.params.id, location]);
const handleSelectAll = isSelected => { const handleSelectAll = isSelected => {
setSelected(isSelected ? [...hosts] : []); setSelected(isSelected ? [...hosts] : []);
@@ -88,7 +88,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
try { try {
const { const {
data: { count, results }, data: { count, results },
} = await fetchHosts(inventory.id, location.search); } = await fetchHosts(match.params.id, location.search);
setHosts(results); setHosts(results);
setHostCount(count); setHostCount(count);

View File

@@ -81,7 +81,7 @@ describe('<InventoryHosts />', () => {
}, },
}); });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InventoryHosts inventory={mockInventory} />); wrapper = mountWithContexts(<InventoryHosts />);
}); });
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
}); });