resolves crashing details view

This commit is contained in:
Alex Corey
2021-06-11 10:41:07 -04:00
committed by Shane McDonald
parent 04839a037a
commit 9992bf03b0
16 changed files with 1339 additions and 1265 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ import { CaretLeftIcon } from '@patternfly/react-icons';
import { Card, PageSection } from '@patternfly/react-core'; import { Card, PageSection } from '@patternfly/react-core';
import useRequest from '../../util/useRequest'; import useRequest from '../../util/useRequest';
import { InstanceGroupsAPI } from '../../api'; import { InstanceGroupsAPI, SettingsAPI } from '../../api';
import RoutedTabs from '../../components/RoutedTabs'; import RoutedTabs from '../../components/RoutedTabs';
import ContentError from '../../components/ContentError'; import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading'; import ContentLoading from '../../components/ContentLoading';
@@ -30,12 +30,24 @@ function ContainerGroup({ setBreadcrumb }) {
isLoading, isLoading,
error: contentError, error: contentError,
request: fetchInstanceGroups, request: fetchInstanceGroups,
result: instanceGroup, result: { instanceGroup, defaultExecution },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const { data } = await InstanceGroupsAPI.readDetail(id); const [
return data; { data },
}, [id]) {
data: { DEFAULT_EXECUTION_QUEUE_NAME },
},
] = await Promise.all([
InstanceGroupsAPI.readDetail(id),
SettingsAPI.readAll(),
]);
return {
instanceGroup: data,
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
};
}, [id]),
{ instanceGroup: null, defaultExecution: '' }
); );
useEffect(() => { useEffect(() => {
@@ -109,10 +121,16 @@ function ContainerGroup({ setBreadcrumb }) {
{instanceGroup && ( {instanceGroup && (
<> <>
<Route path="/instance_groups/container_group/:id/edit"> <Route path="/instance_groups/container_group/:id/edit">
<ContainerGroupEdit instanceGroup={instanceGroup} /> <ContainerGroupEdit
instanceGroup={instanceGroup}
defaultExecution={defaultExecution}
/>
</Route> </Route>
<Route path="/instance_groups/container_group/:id/details"> <Route path="/instance_groups/container_group/:id/details">
<ContainerGroupDetails instanceGroup={instanceGroup} /> <ContainerGroupDetails
instanceGroup={instanceGroup}
defaultExecution={defaultExecution}
/>
</Route> </Route>
<Route path="/instance_groups/container_group/:id/jobs"> <Route path="/instance_groups/container_group/:id/jobs">
<JobList <JobList

View File

@@ -6,6 +6,7 @@ import { Button, Label } from '@patternfly/react-core';
import { VariablesDetail } from '../../../components/CodeEditor'; import { VariablesDetail } from '../../../components/CodeEditor';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';
import { CardBody, CardActionsRow } from '../../../components/Card'; import { CardBody, CardActionsRow } from '../../../components/Card';
import DeleteButton from '../../../components/DeleteButton'; import DeleteButton from '../../../components/DeleteButton';
import { import {
@@ -18,7 +19,7 @@ import { jsonToYaml, isJsonString } from '../../../util/yaml';
import { InstanceGroupsAPI } from '../../../api'; import { InstanceGroupsAPI } from '../../../api';
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
function ContainerGroupDetails({ instanceGroup }) { function ContainerGroupDetails({ instanceGroup, defaultExecution }) {
const { id, name } = instanceGroup; const { id, name } = instanceGroup;
const history = useHistory(); const history = useHistory();
@@ -102,7 +103,8 @@ function ContainerGroupDetails({ instanceGroup }) {
{t`Edit`} {t`Edit`}
</Button> </Button>
)} )}
{instanceGroup.summary_fields.user_capabilities && {name !== defaultExecution &&
instanceGroup.summary_fields.user_capabilities &&
instanceGroup.summary_fields.user_capabilities.delete && ( instanceGroup.summary_fields.user_capabilities.delete && (
<DeleteButton <DeleteButton
ouiaId="container-group-detail-delete-button" ouiaId="container-group-detail-delete-button"
@@ -123,7 +125,9 @@ function ContainerGroupDetails({ instanceGroup }) {
onClose={dismissError} onClose={dismissError}
title={t`Error`} title={t`Error`}
variant="error" variant="error"
/> >
<ErrorDetail error={error} />
</AlertModal>
)} )}
</CardBody> </CardBody>
); );

View File

@@ -31,16 +31,13 @@ function InstanceGroup({ setBreadcrumb }) {
isLoading, isLoading,
error: contentError, error: contentError,
request: fetchInstanceGroups, request: fetchInstanceGroups,
result: { instanceGroup, defaultControlPlane, defaultExecution }, result: { instanceGroup, defaultControlPlane },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const [ const [
{ data }, { data },
{ {
data: { data: { DEFAULT_CONTROL_PLANE_QUEUE_NAME },
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
DEFAULT_EXECUTION_QUEUE_NAME,
},
}, },
] = await Promise.all([ ] = await Promise.all([
InstanceGroupsAPI.readDetail(id), InstanceGroupsAPI.readDetail(id),
@@ -49,10 +46,9 @@ function InstanceGroup({ setBreadcrumb }) {
return { return {
instanceGroup: data, instanceGroup: data,
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME, defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
}; };
}, [id]), }, [id]),
{ instanceGroup: {}, defaultControlPlane: '', defaultExecution: '' } { instanceGroup: null, defaultControlPlane: '' }
); );
useEffect(() => { useEffect(() => {
@@ -133,12 +129,14 @@ function InstanceGroup({ setBreadcrumb }) {
<Route path="/instance_groups/:id/edit"> <Route path="/instance_groups/:id/edit">
<InstanceGroupEdit <InstanceGroupEdit
instanceGroup={instanceGroup} instanceGroup={instanceGroup}
defaultExecution={defaultExecution}
defaultControlPlane={defaultControlPlane} defaultControlPlane={defaultControlPlane}
/> />
</Route> </Route>
<Route path="/instance_groups/:id/details"> <Route path="/instance_groups/:id/details">
<InstanceGroupDetails instanceGroup={instanceGroup} /> <InstanceGroupDetails
defaultControlPlane={defaultControlPlane}
instanceGroup={instanceGroup}
/>
</Route> </Route>
<Route path="/instance_groups/:id/instances"> <Route path="/instance_groups/:id/instances">
<InstanceList /> <InstanceList />

View File

@@ -7,6 +7,7 @@ import { Button } from '@patternfly/react-core';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card'; import { CardBody, CardActionsRow } from '../../../components/Card';
import ErrorDetail from '../../../components/ErrorDetail';
import DeleteButton from '../../../components/DeleteButton'; import DeleteButton from '../../../components/DeleteButton';
import { import {
Detail, Detail,
@@ -22,7 +23,7 @@ const Unavailable = styled.span`
color: var(--pf-global--danger-color--200); color: var(--pf-global--danger-color--200);
`; `;
function InstanceGroupDetails({ instanceGroup }) { function InstanceGroupDetails({ instanceGroup, defaultControlPlane }) {
const { id, name } = instanceGroup; const { id, name } = instanceGroup;
const history = useHistory(); const history = useHistory();
@@ -110,7 +111,7 @@ function InstanceGroupDetails({ instanceGroup }) {
{t`Edit`} {t`Edit`}
</Button> </Button>
)} )}
{name !== 'tower' && {name !== defaultControlPlane &&
instanceGroup.summary_fields.user_capabilities && instanceGroup.summary_fields.user_capabilities &&
instanceGroup.summary_fields.user_capabilities.delete && ( instanceGroup.summary_fields.user_capabilities.delete && (
<DeleteButton <DeleteButton
@@ -132,7 +133,9 @@ function InstanceGroupDetails({ instanceGroup }) {
onClose={dismissError} onClose={dismissError}
title={t`Error`} title={t`Error`}
variant="error" variant="error"
/> >
<ErrorDetail error={error} />
</AlertModal>
)} )}
</CardBody> </CardBody>
); );

View File

@@ -5,11 +5,7 @@ import { CardBody } from '../../../components/Card';
import { InstanceGroupsAPI } from '../../../api'; import { InstanceGroupsAPI } from '../../../api';
import InstanceGroupForm from '../shared/InstanceGroupForm'; import InstanceGroupForm from '../shared/InstanceGroupForm';
function InstanceGroupEdit({ function InstanceGroupEdit({ instanceGroup, defaultControlPlane }) {
instanceGroup,
defaultExecution,
defaultControlPlane,
}) {
const history = useHistory(); const history = useHistory();
const [submitError, setSubmitError] = useState(null); const [submitError, setSubmitError] = useState(null);
const detailsUrl = `/instance_groups/${instanceGroup.id}/details`; const detailsUrl = `/instance_groups/${instanceGroup.id}/details`;
@@ -31,7 +27,6 @@ function InstanceGroupEdit({
<CardBody> <CardBody>
<InstanceGroupForm <InstanceGroupForm
instanceGroup={instanceGroup} instanceGroup={instanceGroup}
defaultExecution={defaultExecution}
defaultControlPlane={defaultControlPlane} defaultControlPlane={defaultControlPlane}
onSubmit={handleSubmit} onSubmit={handleSubmit}
submitError={submitError} submitError={submitError}

View File

@@ -56,7 +56,6 @@ describe('<InstanceGroupEdit>', () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InstanceGroupEdit <InstanceGroupEdit
defaultExecution="default"
defaultControlPlane="controlplane" defaultControlPlane="controlplane"
instanceGroup={instanceGroupData} instanceGroup={instanceGroupData}
/>, />,
@@ -77,7 +76,6 @@ describe('<InstanceGroupEdit>', () => {
await act(async () => { await act(async () => {
towerWrapper = mountWithContexts( towerWrapper = mountWithContexts(
<InstanceGroupEdit <InstanceGroupEdit
defaultExecution="default"
defaultControlPlane="controlplane" defaultControlPlane="controlplane"
instanceGroup={{ ...instanceGroupData, name: 'controlplane' }} instanceGroup={{ ...instanceGroupData, name: 'controlplane' }}
/>, />,
@@ -94,28 +92,6 @@ describe('<InstanceGroupEdit>', () => {
).toEqual('controlplane'); ).toEqual('controlplane');
}); });
test('default instance group name can not be updated', async () => {
let towerWrapper;
await act(async () => {
towerWrapper = mountWithContexts(
<InstanceGroupEdit
defaultExecution="default"
defaultControlPlane="controlplane"
instanceGroup={{ ...instanceGroupData, name: 'default' }}
/>,
{
context: { router: { history } },
}
);
});
expect(
towerWrapper.find('input#instance-group-name').prop('disabled')
).toBeTruthy();
expect(
towerWrapper.find('input#instance-group-name').prop('value')
).toEqual('default');
});
test('handleSubmit should call the api and redirect to details page', async () => { test('handleSubmit should call the api and redirect to details page', async () => {
await act(async () => { await act(async () => {
wrapper.find('InstanceGroupForm').invoke('onSubmit')( wrapper.find('InstanceGroupForm').invoke('onSubmit')(

View File

@@ -55,6 +55,30 @@ function InstanceGroupList({
}) { }) {
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const {
error: protectedItemsError,
isloading: isLoadingProtectedItems,
request: fetchProtectedItems,
result: { defaultControlPlane, defaultExecution },
} = useRequest(
useCallback(async () => {
const {
data: {
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
DEFAULT_EXECUTION_QUEUE_NAME,
},
} = await SettingsAPI.readAll();
return {
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
};
}, []),
{ defaultControlPlane: '', defaultExecution: '' }
);
useEffect(() => {
fetchProtectedItems();
}, [fetchProtectedItems]);
const { const {
error: contentError, error: contentError,
@@ -66,32 +90,18 @@ function InstanceGroupList({
actions, actions,
relatedSearchableKeys, relatedSearchableKeys,
searchableKeys, searchableKeys,
defaultControlPlane,
defaultExecution,
}, },
} = useRequest( } = useRequest(
useCallback(async () => { useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search); const params = parseQueryString(QS_CONFIG, location.search);
const [ const [response, responseActions] = await Promise.all([
response,
responseActions,
{
data: {
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
DEFAULT_EXECUTION_QUEUE_NAME,
},
},
] = await Promise.all([
InstanceGroupsAPI.read(params), InstanceGroupsAPI.read(params),
InstanceGroupsAPI.readOptions(), InstanceGroupsAPI.readOptions(),
SettingsAPI.readAll(),
]); ]);
return { return {
instanceGroups: response.data.results, instanceGroups: response.data.results,
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
instanceGroupsCount: response.data.count, instanceGroupsCount: response.data.count,
actions: responseActions.data.actions, actions: responseActions.data.actions,
relatedSearchableKeys: ( relatedSearchableKeys: (
@@ -165,15 +175,17 @@ function InstanceGroupList({
const pluralizedItemName = t`Instance Groups`; const pluralizedItemName = t`Instance Groups`;
let errorMessageDelete = ''; let errorMessageDelete = '';
const notdeletedable = selected.filter(
i => i.name === defaultControlPlane || i.name === defaultExecution
);
if ( if (notdeletedable.length) {
modifiedSelected.some( errorMessageDelete = (
item => <Plural
item.name === defaultControlPlane || item.name === defaultExecution value={notdeletedable.length}
) one="The following Instance Group cannot be deleted"
) { other="The following Instance Groups cannot be deleted"
errorMessageDelete = errorMessageDelete.concat( />
t`The following Instance Group cannot be deleted`
); );
} }
@@ -227,9 +239,14 @@ function InstanceGroupList({
<PageSection> <PageSection>
<Card> <Card>
<PaginatedTable <PaginatedTable
contentError={contentError || settingsRequestError} contentError={
contentError || settingsRequestError || protectedItemsError
}
hasContentLoading={ hasContentLoading={
isLoading || deleteLoading || isSettingsRequestLoading isLoading ||
deleteLoading ||
isSettingsRequestLoading ||
isLoadingProtectedItems
} }
items={instanceGroups} items={instanceGroups}
itemCount={instanceGroupsCount} itemCount={instanceGroupsCount}

View File

@@ -27,6 +27,8 @@ function ContainerGroupFormFields({ instanceGroup }) {
'credential' 'credential'
); );
const [nameField] = useField('name');
const [overrideField] = useField('override'); const [overrideField] = useField('override');
const handleCredentialUpdate = useCallback( const handleCredentialUpdate = useCallback(
@@ -45,6 +47,7 @@ function ContainerGroupFormFields({ instanceGroup }) {
label={t`Name`} label={t`Name`}
type="text" type="text"
validate={required(null)} validate={required(null)}
isDisabled={nameField.value === 'default'}
isRequired isRequired
/> />
<CredentialLookup <CredentialLookup

View File

@@ -10,7 +10,7 @@ import FormActionGroup from '../../../components/FormActionGroup';
import { required, minMaxValue } from '../../../util/validators'; import { required, minMaxValue } from '../../../util/validators';
import { FormColumnLayout } from '../../../components/FormLayout'; import { FormColumnLayout } from '../../../components/FormLayout';
function InstanceGroupFormFields({ defaultExecution, defaultControlPlane }) { function InstanceGroupFormFields({ defaultControlPlane }) {
const [instanceGroupNameField, ,] = useField('name'); const [instanceGroupNameField, ,] = useField('name');
return ( return (
@@ -22,10 +22,7 @@ function InstanceGroupFormFields({ defaultExecution, defaultControlPlane }) {
type="text" type="text"
validate={required(null)} validate={required(null)}
isRequired isRequired
isDisabled={ isDisabled={instanceGroupNameField.value === defaultControlPlane}
instanceGroupNameField.value === defaultExecution ||
instanceGroupNameField.value === defaultControlPlane
}
/> />
<FormField <FormField
id="instance-group-policy-instance-minimum" id="instance-group-policy-instance-minimum"