Merge pull request #6239 from marshmalien/check-host-inventory-id

Check for top-level inventory and host inventory match 

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-03-10 23:13:16 +00:00
committed by GitHub
3 changed files with 83 additions and 69 deletions

View File

@@ -8,6 +8,7 @@ class Inventories extends InstanceGroupsMixin(Base) {
this.readAccessList = this.readAccessList.bind(this); this.readAccessList = this.readAccessList.bind(this);
this.readHosts = this.readHosts.bind(this); this.readHosts = this.readHosts.bind(this);
this.readHostDetail = this.readHostDetail.bind(this);
this.readGroups = this.readGroups.bind(this); this.readGroups = this.readGroups.bind(this);
this.readGroupsOptions = this.readGroupsOptions.bind(this); this.readGroupsOptions = this.readGroupsOptions.bind(this);
this.promoteGroup = this.promoteGroup.bind(this); this.promoteGroup = this.promoteGroup.bind(this);
@@ -27,6 +28,22 @@ class Inventories extends InstanceGroupsMixin(Base) {
return this.http.get(`${this.baseUrl}${id}/hosts/`, { params }); return this.http.get(`${this.baseUrl}${id}/hosts/`, { params });
} }
async readHostDetail(inventoryId, hostId) {
const {
data: { results },
} = await this.http.get(
`${this.baseUrl}${inventoryId}/hosts/?id=${hostId}`
);
if (Array.isArray(results) && results.length) {
return results[0];
}
throw new Error(
`How did you get here? Host not found for Inventory ID: ${inventoryId}`
);
}
readGroups(id, params) { readGroups(id, params) {
return this.http.get(`${this.baseUrl}${id}/groups/`, { params }); return this.http.get(`${this.baseUrl}${id}/groups/`, { params });
} }

View File

@@ -11,7 +11,7 @@ import {
} from 'react-router-dom'; } from 'react-router-dom';
import useRequest from '@util/useRequest'; import useRequest from '@util/useRequest';
import { HostsAPI } from '@api'; import { InventoriesAPI } from '@api';
import { Card, CardActions } from '@patternfly/react-core'; import { Card, CardActions } from '@patternfly/react-core';
import { CaretLeftIcon } from '@patternfly/react-icons'; import { CaretLeftIcon } from '@patternfly/react-icons';
import { TabbedCardHeader } from '@components/Card'; import { TabbedCardHeader } from '@components/Card';
@@ -35,12 +35,14 @@ function InventoryHost({ i18n, setBreadcrumb, inventory }) {
request: fetchHost, request: fetchHost,
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const { data } = await HostsAPI.readDetail(match.params.hostId); const response = await InventoriesAPI.readHostDetail(
inventory.id,
match.params.hostId
);
return { return {
host: data, host: response,
}; };
}, [match.params.hostId]), // eslint-disable-line react-hooks/exhaustive-deps }, [inventory.id, match.params.hostId]),
{ {
host: null, host: null,
} }
@@ -48,7 +50,7 @@ function InventoryHost({ i18n, setBreadcrumb, inventory }) {
useEffect(() => { useEffect(() => {
fetchHost(); fetchHost();
}, [fetchHost]); }, [fetchHost, location.pathname]);
useEffect(() => { useEffect(() => {
if (inventory && host) { if (inventory && host) {
@@ -89,24 +91,7 @@ function InventoryHost({ i18n, setBreadcrumb, inventory }) {
}, },
]; ];
let cardHeader = ( if (contentError) {
<TabbedCardHeader>
<RoutedTabs tabsArray={tabsArray} />
<CardActions>
<CardCloseButton linkTo={hostListUrl} />
</CardActions>
</TabbedCardHeader>
);
if (location.pathname.endsWith('edit')) {
cardHeader = null;
}
if (isLoading) {
return <ContentLoading />;
}
if (!isLoading && contentError) {
return ( return (
<Card> <Card>
<ContentError error={contentError}> <ContentError error={contentError}>
@@ -125,48 +110,51 @@ function InventoryHost({ i18n, setBreadcrumb, inventory }) {
return ( return (
<> <>
{cardHeader} {['edit'].some(name => location.pathname.includes(name)) ? null : (
<TabbedCardHeader>
<RoutedTabs tabsArray={tabsArray} />
<CardActions>
<CardCloseButton linkTo={hostListUrl} />
</CardActions>
</TabbedCardHeader>
)}
{isLoading && <ContentLoading />}
{!isLoading && host && (
<Switch> <Switch>
<Redirect <Redirect
from="/inventories/inventory/:id/hosts/:hostId" from="/inventories/inventory/:id/hosts/:hostId"
to="/inventories/inventory/:id/hosts/:hostId/details" to="/inventories/inventory/:id/hosts/:hostId/details"
exact exact
/> />
{host &&
inventory && [
<Route <Route
key="details" key="details"
path="/inventories/inventory/:id/hosts/:hostId/details" path="/inventories/inventory/:id/hosts/:hostId/details"
> >
<InventoryHostDetail host={host} /> <InventoryHostDetail host={host} />
</Route>, </Route>
<Route <Route
key="edit" key="edit"
path="/inventories/inventory/:id/hosts/:hostId/edit" path="/inventories/inventory/:id/hosts/:hostId/edit"
> >
<InventoryHostEdit host={host} inventory={inventory} /> <InventoryHostEdit host={host} inventory={inventory} />
</Route>, </Route>
<Route <Route
key="completed-jobs" key="completed-jobs"
path="/inventories/inventory/:id/hosts/:hostId/completed_jobs" path="/inventories/inventory/:id/hosts/:hostId/completed_jobs"
> >
<JobList defaultParams={{ job__hosts: host.id }} /> <JobList defaultParams={{ job__hosts: host.id }} />
</Route>, </Route>
]} <Route key="not-found" path="*">
<Route
key="not-found"
path="*"
render={() =>
!isLoading && (
<ContentError isNotFound> <ContentError isNotFound>
<Link to={`${match.url}/details`}> <Link to={`${match.url}/details`}>
{i18n._(`View Inventory Host Details`)} {i18n._(`View Inventory Host Details`)}
</Link> </Link>
</ContentError> </ContentError>
) </Route>
}
/>
</Switch> </Switch>
)}
</> </>
); );
} }

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { HostsAPI } from '@api'; import { InventoriesAPI } from '@api';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import mockHost from '../shared/data.host.json'; import mockHost from '../shared/data.host.json';
import InventoryHost from './InventoryHost'; import InventoryHost from './InventoryHost';
@@ -15,7 +15,7 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
HostsAPI.readDetail.mockResolvedValue({ InventoriesAPI.readHostDetail.mockResolvedValue({
data: { ...mockHost }, data: { ...mockHost },
}); });
@@ -54,7 +54,7 @@ describe('<InventoryHost />', () => {
}); });
test('should show content error when api throws error on initial render', async () => { test('should show content error when api throws error on initial render', async () => {
HostsAPI.readDetail.mockRejectedValueOnce(new Error()); InventoriesAPI.readHostDetail.mockRejectedValueOnce(new Error());
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventoryHost inventory={mockInventory} setBreadcrumb={() => {}} /> <InventoryHost inventory={mockInventory} setBreadcrumb={() => {}} />
@@ -76,4 +76,13 @@ describe('<InventoryHost />', () => {
}); });
await waitForElement(wrapper, 'ContentError', el => el.length === 1); await waitForElement(wrapper, 'ContentError', el => el.length === 1);
}); });
test('should show content error when inventory id does not match host inventory', async () => {
await act(async () => {
wrapper = mountWithContexts(
<InventoryHost inventory={{ id: 99 }} setBreadcrumb={() => {}} />
);
});
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
});
}); });