Skip to content

Commit bc17cc8

Browse files
committed
feature(crr): select the destination S3 endpoint from Verify's discovered list
1 parent 70ca8fd commit bc17cc8

6 files changed

Lines changed: 73 additions & 9 deletions

File tree

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,17 @@ const fillValidForm = async () => {
3131
};
3232

3333
describe('CRRSetupWizard — Configure step', () => {
34-
it('confirms the destination is reachable when the user clicks Check Connection', async () => {
34+
it('confirms the destination is reachable and offers its discovered S3 endpoints to pick from', async () => {
3535
server.use(
3636
rest.post(VERIFY_URL, (_req, res, ctx) =>
37-
res(ctx.json({ ok: true, mode: 'management-network', instanceName: 'ageless-valley' })),
37+
res(
38+
ctx.json({
39+
ok: true,
40+
mode: 'management-network',
41+
instanceName: 'ageless-valley',
42+
s3Endpoints: ['s3.dest.example', 's3-alt.dest.example'],
43+
}),
44+
),
3845
),
3946
);
4047
render(<CRRSetupWizard />, { wrapper: Wrapper });
@@ -43,6 +50,10 @@ describe('CRRSetupWizard — Configure step', () => {
4350
await userEvent.click(screen.getByRole('button', { name: /Check Connection/i }));
4451

4552
expect(await screen.findByText(/Destination reachable/i)).toBeInTheDocument();
53+
// The discovered endpoints become the selectable replication targets.
54+
await userEvent.click(screen.getByText(/Select the destination S3 endpoint/i));
55+
expect(await screen.findByText('s3.dest.example')).toBeInTheDocument();
56+
expect(screen.getByText('s3-alt.dest.example')).toBeInTheDocument();
4657
});
4758

4859
it('surfaces the ARTESCA problem code when Check Connection is rejected', async () => {
@@ -70,7 +81,7 @@ describe('CRRSetupWizard — Configure step', () => {
7081
expect(await screen.findByText(/pasted certificate is not a valid PEM/i)).toBeInTheDocument();
7182
});
7283

73-
it('blocks the user on Configure with an error toast when the silent verify fails on Continue', async () => {
84+
it('surfaces the unreachable error when Check Connection fails', async () => {
7485
server.use(
7586
rest.post(VERIFY_URL, (_req, res, ctx) =>
7687
res(
@@ -90,7 +101,7 @@ describe('CRRSetupWizard — Configure step', () => {
90101
render(<CRRSetupWizard />, { wrapper: Wrapper });
91102

92103
await fillValidForm();
93-
await userEvent.click(screen.getByRole('button', { name: /Continue/i }));
104+
await userEvent.click(screen.getByRole('button', { name: /Check Connection/i }));
94105

95106
expect(await screen.findByText(/destination cluster did not respond/i)).toBeInTheDocument();
96107
});
@@ -117,7 +128,9 @@ describe('CRRSetupWizard — Configure step', () => {
117128
);
118129
}
119130
retryBody = req.body as { hostAliases?: { hostname: string; ip: string }[] };
120-
return res(ctx.json({ ok: true, mode: 'management-network', instanceName: 'ageless-valley' }));
131+
return res(
132+
ctx.json({ ok: true, mode: 'management-network', instanceName: 'ageless-valley', s3Endpoints: ['s3.dest.example'] }),
133+
);
121134
}),
122135
);
123136
render(<CRRSetupWizard />, { wrapper: Wrapper });

src/react/locations/CRRSetupWizard/api/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ export type VerifyResponse =
3434
mode: 'management-network';
3535
instanceName: string;
3636
artescaVersion?: string;
37+
s3Endpoints?: string[];
3738
}
38-
| { ok: true; mode: 'data-network' };
39+
| { ok: true; mode: 'data-network'; s3Endpoints?: string[] };
3940

4041
export type StartSetupBody = {
4142
destinationConnection: DestinationConnection;
4243
destinationCertificate: string;
4344
destinationAccount: { mode: 'create' | 'existing'; name: string };
45+
/** The destination S3 endpoint the user picked from Verify's discovered list. */
46+
replicationEndpoint: string;
4447
targetBucket?: string;
4548
hostAliases?: HostAlias[];
4649
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const VALUES: ConfigureFormValues = {
3939
username: 'scality',
4040
password: 'super-secret',
4141
certificate: '-----BEGIN CERTIFICATE-----\nx\n-----END CERTIFICATE-----',
42+
replicationEndpoint: 'https://s3.dest.example',
4243
destinationAccountName: 'crr-dest',
4344
hostAliases: [],
4445
createReplicationRule: true,

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const ConfigureStep = () => {
5656
const verify = useCRRConfigurationVerifyMutation();
5757
const lastVerifiedRef = useRef<string | null>(null);
5858
const [dnsFallbackHosts, setDnsFallbackHosts] = useState<string[] | null>(null);
59+
const [discoveredEndpoints, setDiscoveredEndpoints] = useState<string[]>([]);
5960

6061
const formMethods = useForm<ConfigureFormValues>({
6162
mode: 'all',
@@ -82,6 +83,7 @@ export const ConfigureStep = () => {
8283
const runVerify = async (body: VerifyRequestBody): Promise<VerifyResponse> => {
8384
const response = await verify.mutateAsync(body);
8485
lastVerifiedRef.current = JSON.stringify(body);
86+
setDiscoveredEndpoints(response.s3Endpoints ?? []);
8587
return response;
8688
};
8789

@@ -162,7 +164,11 @@ export const ConfigureStep = () => {
162164
}
163165
>
164166
<SourceSection />
165-
<DestinationConnectionSection isCheckingConnection={verify.isLoading} onCheckConnection={onCheckConnection} />
167+
<DestinationConnectionSection
168+
isCheckingConnection={verify.isLoading}
169+
onCheckConnection={onCheckConnection}
170+
discoveredEndpoints={discoveredEndpoints}
171+
/>
166172
<DestinationAccountSection />
167173
<ReplicationSection />
168174
</Form>

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { FormGroup, FormSection, Radio, Stack, Wrap } from '@scality/core-ui';
2-
import { Button, Input } from '@scality/core-ui/dist/next';
2+
import { Button, Input, Select } from '@scality/core-ui/dist/next';
33
import { Controller, useFormContext } from 'react-hook-form';
44
import { CertificateSection } from '../../../../ui-elements/CertificateSection';
55
import type { ConfigureFormValues } from './schema';
66

77
type Props = {
88
isCheckingConnection: boolean;
99
onCheckConnection: () => void;
10+
discoveredEndpoints: string[];
1011
};
1112

12-
export const DestinationConnectionSection = ({ isCheckingConnection, onCheckConnection }: Props) => {
13+
export const DestinationConnectionSection = ({
14+
isCheckingConnection,
15+
onCheckConnection,
16+
discoveredEndpoints,
17+
}: Props) => {
1318
const {
1419
control,
1520
register,
@@ -122,6 +127,36 @@ export const DestinationConnectionSection = ({ isCheckingConnection, onCheckConn
122127
onClick={onCheckConnection}
123128
/>
124129
</Wrap>
130+
{discoveredEndpoints.length > 0 && (
131+
<FormGroup
132+
id="replicationEndpoint"
133+
direction="horizontal"
134+
label="Replication Endpoint"
135+
required
136+
helpErrorPosition="bottom"
137+
error={errorIfTouched('replicationEndpoint')}
138+
content={
139+
<Controller
140+
name="replicationEndpoint"
141+
control={control}
142+
render={({ field }) => (
143+
<Select
144+
id="replicationEndpoint"
145+
value={field.value}
146+
onChange={(value) => field.onChange(value)}
147+
placeholder="Select the destination S3 endpoint"
148+
>
149+
{discoveredEndpoints.map((endpoint) => (
150+
<Select.Option key={endpoint} value={endpoint}>
151+
{endpoint}
152+
</Select.Option>
153+
))}
154+
</Select>
155+
)}
156+
/>
157+
}
158+
/>
159+
)}
125160
</FormSection>
126161
);
127162
};

src/react/locations/CRRSetupWizard/steps/ConfigureStep/schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export type ConfigureFormValues = {
1919
password: string;
2020
certificate: string;
2121

22+
/** Destination S3 endpoint chosen from the list Verify discovers. */
23+
replicationEndpoint: string;
24+
2225
destinationAccountName: string;
2326

2427
hostAliases: HostAlias[];
@@ -39,6 +42,7 @@ export const defaultConfigureValues: ConfigureFormValues = {
3942
username: '',
4043
password: '',
4144
certificate: '',
45+
replicationEndpoint: '',
4246
destinationAccountName: '',
4347
hostAliases: [],
4448
createReplicationRule: false,
@@ -84,6 +88,7 @@ export const configureSchema = Joi.object<ConfigureFormValues>({
8488
}),
8589
username: Joi.string().required(),
8690
password: Joi.string().required(),
91+
replicationEndpoint: Joi.string().required(),
8792
certificate: Joi.string()
8893
.pattern(/-----BEGIN CERTIFICATE-----/)
8994
.required()
@@ -158,6 +163,7 @@ export const toStartSetupBody = (values: ConfigureFormValues): StartSetupBody =>
158163
const body: StartSetupBody = {
159164
...verifyBody,
160165
destinationAccount: { mode: 'create', name: values.destinationAccountName },
166+
replicationEndpoint: values.replicationEndpoint,
161167
};
162168
if (values.createReplicationRule && values.targetBucketName) {
163169
body.targetBucket = values.targetBucketName;

0 commit comments

Comments
 (0)