mirror of
https://github.com/ansible/awx.git
synced 2026-01-18 13:11:19 -03:30
Update breadcrumb and fetch new hosts when url changes
This commit is contained in:
parent
fa144aa98f
commit
faa0802d97
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { Card, CardHeader, PageSection } from '@patternfly/react-core';
|
||||
@ -16,186 +16,152 @@ import InventorySources from './InventorySources';
|
||||
import { InventoriesAPI } from '@api';
|
||||
import InventoryEdit from './InventoryEdit';
|
||||
|
||||
class Inventory extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
function Inventory({ history, i18n, location, match, setBreadcrumb }) {
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||
const [inventory, setInventory] = useState(null);
|
||||
|
||||
this.state = {
|
||||
contentError: null,
|
||||
hasContentLoading: true,
|
||||
inventory: null,
|
||||
};
|
||||
this.loadInventory = this.loadInventory.bind(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.loadInventory();
|
||||
}
|
||||
|
||||
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;
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const { data } = await InventoriesAPI.readDetail(match.params.id);
|
||||
setBreadcrumb(data);
|
||||
setInventory(data);
|
||||
} catch (error) {
|
||||
setContentError(error);
|
||||
} finally {
|
||||
setHasContentLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasContentLoading && contentError) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
fetchData();
|
||||
}, [match.params.id, setBreadcrumb]);
|
||||
|
||||
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 (
|
||||
<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 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>
|
||||
<ContentError error={contentError}>
|
||||
{contentError.response.status === 404 && (
|
||||
<span>
|
||||
{i18n._(`Inventory not found.`)}{' '}
|
||||
<Link to="/inventories">{i18n._(`View all Inventories.`)}</Link>
|
||||
</span>
|
||||
)}
|
||||
</ContentError>
|
||||
</Card>
|
||||
</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 };
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { InventoriesAPI } from '@api';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
@ -12,41 +13,38 @@ InventoriesAPI.readDetail.mockResolvedValue({
|
||||
});
|
||||
|
||||
describe('<Inventory />', () => {
|
||||
test('initially renders succesfully', async done => {
|
||||
const wrapper = mountWithContexts(
|
||||
<Inventory setBreadcrumb={() => {}} match={{ params: { id: 1 } }} />
|
||||
);
|
||||
await waitForElement(
|
||||
wrapper,
|
||||
'Inventory',
|
||||
el => el.state('hasContentLoading') === true
|
||||
);
|
||||
await waitForElement(
|
||||
wrapper,
|
||||
'Inventory',
|
||||
el => el.state('hasContentLoading') === false
|
||||
);
|
||||
let wrapper;
|
||||
|
||||
test('initially renders succesfully', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Inventory setBreadcrumb={() => {}} match={{ params: { id: 1 } }} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
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 () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/inventories/inventory/1/foobar'],
|
||||
});
|
||||
const wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />, {
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match: {
|
||||
params: { id: 1 },
|
||||
url: '/inventories/inventory/1/foobar',
|
||||
path: '/inventories/inventory/1/foobar',
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />, {
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match: {
|
||||
params: { id: 1 },
|
||||
url: '/inventories/inventory/1/foobar',
|
||||
path: '/inventories/inventory/1/foobar',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||
});
|
||||
|
||||
@ -20,7 +20,7 @@ const QS_CONFIG = getQSConfig('host', {
|
||||
order_by: 'name',
|
||||
});
|
||||
|
||||
function InventoryHosts({ i18n, location, match, inventory }) {
|
||||
function InventoryHosts({ i18n, location, match }) {
|
||||
const [actions, setActions] = useState(null);
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [deletionError, setDeletionError] = useState(null);
|
||||
@ -47,7 +47,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
|
||||
data: { actions: optionActions },
|
||||
},
|
||||
] = await Promise.all([
|
||||
fetchHosts(inventory.id, location.search),
|
||||
fetchHosts(match.params.id, location.search),
|
||||
InventoriesAPI.readOptions(),
|
||||
]);
|
||||
|
||||
@ -62,7 +62,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
|
||||
}
|
||||
|
||||
fetchData();
|
||||
}, [inventory, location]);
|
||||
}, [match.params.id, location]);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...hosts] : []);
|
||||
@ -88,7 +88,7 @@ function InventoryHosts({ i18n, location, match, inventory }) {
|
||||
try {
|
||||
const {
|
||||
data: { count, results },
|
||||
} = await fetchHosts(inventory.id, location.search);
|
||||
} = await fetchHosts(match.params.id, location.search);
|
||||
|
||||
setHosts(results);
|
||||
setHostCount(count);
|
||||
|
||||
@ -81,7 +81,7 @@ describe('<InventoryHosts />', () => {
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<InventoryHosts inventory={mockInventory} />);
|
||||
wrapper = mountWithContexts(<InventoryHosts />);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user