Skip to content

Commit 4ebee9a

Browse files
committed
bugfix(crr): address Configure & Apply Actions UI review findings
1 parent c0565cd commit 4ebee9a

5 files changed

Lines changed: 75 additions & 26 deletions

File tree

src/react/locations/CRRSetupWizard/CRRSetupWizard.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,37 @@ describe('CRRSetupWizard — Configure step', () => {
143143
{ hostname: 'iam.dest.local', ip: '10.0.0.9' },
144144
]);
145145
});
146+
147+
it('reopens the DNS modal when the mapped IP leaves the destination unreachable', async () => {
148+
let verifyCalls = 0;
149+
server.use(
150+
rest.post(VERIFY_URL, (_req, res, ctx) => {
151+
verifyCalls += 1;
152+
const code = verifyCalls === 1 ? 'DestinationDnsResolutionFailed' : 'DestinationUnreachable';
153+
return res(
154+
ctx.status(502),
155+
ctx.set('Content-Type', 'application/problem+json'),
156+
ctx.body(
157+
JSON.stringify({
158+
type: 'about:blank',
159+
title: 'error',
160+
status: 502,
161+
code,
162+
...(verifyCalls === 1 ? { unresolvedHosts: ['s3.dest.local'] } : {}),
163+
}),
164+
),
165+
);
166+
}),
167+
);
168+
render(<CRRSetupWizard />, { wrapper: Wrapper });
169+
170+
fillValidForm();
171+
await clickWhenEnabled(/Check Connection/i);
172+
expect(await screen.findByText('• s3.dest.local')).toBeInTheDocument();
173+
174+
await userEvent.type(screen.getByRole('textbox', { name: /Destination cluster IP/i }), '10.0.0.9');
175+
await clickWhenEnabled(/Retry Connection/i);
176+
177+
expect(await screen.findByText('• s3.dest.local')).toBeInTheDocument();
178+
});
146179
});

src/react/locations/CRRSetupWizard/steps/ApplyActionsStep/ApplyActionsStep.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Form, Icon, Loader, Stack, Text } from '@scality/core-ui';
1+
import { Banner, Form, Icon, Loader, Stack, Text } from '@scality/core-ui';
22
import { useStepper } from '@scality/core-ui/dist/components/steppers/Stepper.component';
33
import { Button } from '@scality/core-ui/dist/next';
44
import { useCreateBucket, useSetBucketReplication, useSetBucketVersioning } from '@scality/data-browser-library';
@@ -57,7 +57,6 @@ const StatusCell = ({ view, onRetry }: { view: StepView; onRetry: () => void })
5757
<Icon name="Exclamation-circle" color={theme.statusCritical} />
5858
<span>Failed</span>
5959
<Button icon={<Icon name="Redo" />} variant="secondary" type="button" label="Retry" onClick={onRetry} />
60-
{view.errorMessage && <ErrorText>{view.errorMessage}</ErrorText>}
6160
</StatusBox>
6261
);
6362
}
@@ -347,23 +346,31 @@ export const ApplyActionsStep = (props: Props) => {
347346
style={{ width: '50rem' }}
348347
>
349348
{Slots}
349+
{stepViews.some((view) => view.id === 'create-location' && view.active) && (
350+
<Banner variant="warning" icon={<Icon color="statusWarning" name="Exclamation-circle" />}>
351+
Creating the location can take up to a minute while it is applied to the running configuration.
352+
</Banner>
353+
)}
350354
<div style={{ height: '32rem', overflow: 'auto' }}>
351355
<Table>
352356
<T.Head>
353357
<T.HeadRow style={{ display: 'flex' }}>
354358
<T.HeadCell style={{ width: '150px' }}>Step</T.HeadCell>
355-
<T.HeadCell style={{ width: '50%' }}>Action</T.HeadCell>
356-
<T.HeadCell style={{ width: '12.5%' }}>Status</T.HeadCell>
359+
<T.HeadCell style={{ flex: 1, minWidth: 0 }}>Action</T.HeadCell>
360+
<T.HeadCell style={{ width: '200px' }}>Status</T.HeadCell>
357361
</T.HeadRow>
358362
</T.Head>
359363
<T.Body>
360364
{stepViews.map((view) => (
361365
<T.Row key={view.id} style={{ display: 'flex' }}>
362366
<T.Cell style={{ width: '150px' }}>{view.step}</T.Cell>
363-
<T.Cell style={{ width: '50%' }}>
364-
<Text>{view.label}</Text>
367+
<T.Cell style={{ flex: 1, minWidth: 0 }}>
368+
<Stack direction="vertical" gap="r4">
369+
<Text>{view.label}</Text>
370+
{view.state === 'failed' && view.errorMessage && <ErrorText>{view.errorMessage}</ErrorText>}
371+
</Stack>
365372
</T.Cell>
366-
<T.Cell style={{ width: '12.5%' }}>
373+
<T.Cell style={{ width: '200px' }}>
367374
<StatusCell view={view} onRetry={() => onRetry(view)} />
368375
</T.Cell>
369376
</T.Row>

src/react/locations/CRRSetupWizard/steps/ConfigureStep/ConfigureStep.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const unresolvedHostsFrom = (error: unknown): string[] | null => {
4343
return null;
4444
};
4545

46+
const isDestinationUnreachable = (error: unknown): boolean =>
47+
error instanceof ServiceError && error.code === 'DestinationUnreachable';
48+
4649
const mergeAliases = (existing: HostAlias[], added: HostAlias[]): HostAlias[] => {
4750
const byHost = new Map(existing.map((alias) => [alias.hostname, alias]));
4851
for (const alias of added) byHost.set(alias.hostname, alias);
@@ -76,6 +79,11 @@ export const ConfigureStep = () => {
7679
setDnsFallbackHosts(hosts);
7780
return;
7881
}
82+
const aliases = getValues('hostAliases');
83+
if (aliases.length > 0 && isDestinationUnreachable(error)) {
84+
setDnsFallbackHosts(aliases.map((alias) => alias.hostname));
85+
return;
86+
}
7987
showToast({ open: true, status: 'error', message: errorMessage(error) });
8088
};
8189

src/react/locations/CRRSetupWizard/steps/ConfigureStep/DestinationConnectionSection.tsx

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { FormGroup, FormSection, Icon, Radio, Stack, Text, Wrap } from '@scality/core-ui';
1+
import { FormGroup, FormSection, Icon, Stack, Text, Wrap } from '@scality/core-ui';
22
import { Button, Input } from '@scality/core-ui/dist/next';
33
import { Controller, useFormContext } from 'react-hook-form';
4+
import { RadioGroup } from '../../../../ISV/components/RadioGroup';
45
import { CertificateSection } from '../../../../ui-elements/CertificateSection';
56
import type { ConfigureFormValues } from './schema';
67

@@ -47,22 +48,16 @@ export const DestinationConnectionSection = ({
4748
name="connectionMode"
4849
control={control}
4950
render={({ field }) => (
50-
<Stack direction="horizontal" gap="r16">
51-
<Radio
52-
name="connectionMode"
53-
value="management-network"
54-
checked={field.value === 'management-network'}
55-
onChange={() => field.onChange('management-network')}
56-
label="Management Network"
57-
/>
58-
<Radio
59-
name="connectionMode"
60-
value="data-network"
61-
checked={field.value === 'data-network'}
62-
onChange={() => field.onChange('data-network')}
63-
label="Data Network"
64-
/>
65-
</Stack>
51+
<RadioGroup
52+
name="connectionMode"
53+
options={[
54+
{ value: 'management-network', label: 'Management Network' },
55+
{ value: 'data-network', label: 'Data Network' },
56+
]}
57+
value={field.value}
58+
onChange={(next) => field.onChange(next)}
59+
direction="horizontal"
60+
/>
6661
)}
6762
/>
6863
}
@@ -86,7 +81,9 @@ export const DestinationConnectionSection = ({
8681
required
8782
helpErrorPosition="bottom"
8883
error={errorIfTouched('baseDomain')}
89-
content={<Input id="baseDomain" {...register('baseDomain')} />}
84+
content={
85+
<Input id="baseDomain" noPlaceholderPrefix placeholder="ui.<base-domain>" {...register('baseDomain')} />
86+
}
9087
/>
9188
)}
9289
{connectionMode === 'data-network' && (

src/react/ui-elements/CertificateSection.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dropzone, Stack, Text, TextArea } from '@scality/core-ui';
1+
import { Dropzone, Stack, spacing, Text, TextArea } from '@scality/core-ui';
22
import type { ChangeEvent } from 'react';
33
import { useFormContext } from 'react-hook-form';
44
import styled from 'styled-components';
@@ -13,6 +13,10 @@ const CertificateContainer = styled.div`
1313
gap: 0.5rem;
1414
color: ${(props) => props.theme.textPrimary};
1515
max-width: 45rem;
16+
17+
& textarea {
18+
border: ${spacing.r1} solid transparent;
19+
}
1620
`;
1721

1822
const PLACEHOLDER = `Example:

0 commit comments

Comments
 (0)