Skip to content

Commit f022c46

Browse files
committed
Merge branch 'bugfix/ARTESCA-16527-add-bucket-name-and-role-fields' into q/4.1
2 parents 0be7e6e + 2d7b3e3 commit f022c46

File tree

7 files changed

+973
-119
lines changed

7 files changed

+973
-119
lines changed

src/react/workflow/ConfigurationTab.tsx

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import { useManagementClient } from '../ManagementProvider';
3939
import { useInstanceId } from '../next-architecture/ui/AuthProvider';
4040
import { workflowListQuery } from '../queries';
4141
import { useRolePathName } from '../utils/hooks';
42+
import { Resolver, ResolverOptions } from 'react-hook-form';
43+
import { useAccountsLocationsEndpointsAdapter } from '../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider';
44+
import { useAccountsLocationsAndEndpoints } from '../next-architecture/domain/business/accounts';
45+
import { getLocationTypeKey } from '../utils/storageOptions';
4246
import { DeleteWorkflowButton } from './DeleteWorkflowButton';
4347
import {
4448
ExpirationForm,
@@ -49,6 +53,7 @@ import ReplicationForm, {
4953
disallowedPrefixes,
5054
GeneralReplicationGroup,
5155
replicationSchema,
56+
validateCRRFields,
5257
} from './ReplicationForm';
5358
import {
5459
GeneralTransitionGroup,
@@ -506,38 +511,112 @@ function EditForm({
506511
s.source.prefix !== '',
507512
);
508513

514+
const accountsLocationsEndpointsAdapter =
515+
useAccountsLocationsEndpointsAdapter();
516+
const { accountsLocationsAndEndpoints } = useAccountsLocationsAndEndpoints({
517+
accountsLocationsEndpointsAdapter,
518+
});
519+
520+
// Get CRR location names
521+
const crrLocationNames =
522+
accountsLocationsAndEndpoints?.locations
523+
?.filter(
524+
(location) =>
525+
getLocationTypeKey(location) === 'location-scality-crr-v1',
526+
)
527+
.map((location) => location.name) || [];
528+
509529
const schema =
510530
workflow && isExpirationWorkflow(workflow)
511531
? expirationSchema
512532
: isTransitionWorkflow(workflow)
513533
? Joi.object(transitionSchema)
514534
: Joi.object(
515-
//@ts-expect-error fix this when you are working on it
516535
replicationSchema(
517536
[],
518537
disallowedPrefixes(
519-
//@ts-expect-error fix this when you are working on it
520-
workflow.source.bucketName,
538+
(workflow as Replication).source.bucketName,
521539
workflows.replications,
522540
).filter(
523541
(s) =>
524-
//@ts-expect-error fix this when you are working on it
525-
s !== workflow.source.prefix,
542+
s !== (workflow as Replication).source.prefix,
526543
),
527544
isPrefixMandatory,
545+
false,
528546
),
529547
);
530548

549+
// Custom resolver that preserves React Hook Form validation errors and runs Joi validation
550+
const customResolver: Resolver<Record<string, unknown>> = async (
551+
values,
552+
context,
553+
options,
554+
) => {
555+
// Check for CRR-related validation errors using the shared validation function
556+
// In ConfigurationTab, ReplicationForm uses empty prefix, so fields are at root level
557+
const existingErrors: Record<string, { type: string; message: string }> = {};
558+
let hasExistingErrors = false;
559+
560+
const formValues = values as {
561+
destinationLocation?: string[];
562+
destinationBucketName?: string;
563+
destinationRole?: string;
564+
};
565+
566+
const crrValidationErrors = validateCRRFields(
567+
formValues.destinationLocation,
568+
crrLocationNames,
569+
formValues.destinationBucketName,
570+
formValues.destinationRole,
571+
);
572+
573+
if (crrValidationErrors.bucketNameError) {
574+
existingErrors.destinationBucketName = {
575+
type: 'validate',
576+
message: crrValidationErrors.bucketNameError,
577+
};
578+
hasExistingErrors = true;
579+
}
580+
581+
if (crrValidationErrors.roleError) {
582+
existingErrors.destinationRole = {
583+
type: 'validate',
584+
message: crrValidationErrors.roleError,
585+
};
586+
hasExistingErrors = true;
587+
}
588+
589+
// Run Joi validation
590+
const joiValidator = joiResolver(schema);
591+
let joiResult;
592+
593+
if (workflow && isExpirationWorkflow(workflow)) {
594+
joiResult = await joiValidator(
595+
prepareExpirationQuery(values as unknown as BucketWorkflowExpirationV1),
596+
context,
597+
options as unknown as ResolverOptions<BucketWorkflowExpirationV1>,
598+
);
599+
} else {
600+
joiResult = await joiValidator(values, context, options as ResolverOptions<Record<string, unknown>>);
601+
}
602+
603+
// Merge existing validation errors with Joi errors, giving priority to existing errors
604+
if (hasExistingErrors) {
605+
return {
606+
values: joiResult.values,
607+
errors: {
608+
...(joiResult.errors as Record<string, unknown>),
609+
...existingErrors,
610+
},
611+
};
612+
}
613+
614+
return joiResult;
615+
};
616+
531617
const useFormMethods = useForm({
532618
mode: 'all',
533-
resolver: async (values, context, options) => {
534-
const joiValidator = joiResolver(schema);
535-
if (workflow && isExpirationWorkflow(workflow)) {
536-
return joiValidator(prepareExpirationQuery(values), context, options);
537-
} else {
538-
return joiValidator(values, context, options);
539-
}
540-
},
619+
resolver: customResolver,
541620
// fix this when you are working on it
542621
defaultValues: isExpirationWorkflow(workflow)
543622
? initDefaultValues(workflow)

src/react/workflow/CreateWorkflow.tsx

Lines changed: 135 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { convertRemToPixels } from '@scality/core-ui/dist/utils';
1111
import {
1212
Controller,
1313
FormProvider,
14+
Resolver,
15+
ResolverOptions,
1416
SubmitHandler,
1517
useForm,
1618
} from 'react-hook-form';
@@ -39,6 +41,8 @@ import {
3941
} from '../next-architecture/domain/business/buckets';
4042
import { useLocationAndStorageInfos } from '../next-architecture/domain/business/locations';
4143
import { useAccountsLocationsEndpointsAdapter } from '../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider';
44+
import { useAccountsLocationsAndEndpoints } from '../next-architecture/domain/business/accounts';
45+
import { getLocationTypeKey } from '../utils/storageOptions';
4246
import { useInstanceId } from '../next-architecture/ui/AuthProvider';
4347
import { workflowListQuery } from '../queries';
4448
import { useQueryParams, useRolePathName } from '../utils/hooks';
@@ -51,6 +55,7 @@ import ReplicationForm, {
5155
GeneralReplicationGroup,
5256
disallowedPrefixes,
5357
replicationSchema,
58+
validateCRRFields,
5459
} from './ReplicationForm';
5560
import {
5661
GeneralTransitionGroup,
@@ -134,55 +139,136 @@ const CreateWorkflow = () => {
134139
locationInfos.status === 'success' &&
135140
locationInfos.value.location?.isTransient;
136141

142+
const { accountsLocationsAndEndpoints } = useAccountsLocationsAndEndpoints({
143+
accountsLocationsEndpointsAdapter,
144+
});
145+
146+
// Get CRR location names
147+
const crrLocationNames =
148+
accountsLocationsAndEndpoints?.locations
149+
?.filter(
150+
(location) =>
151+
getLocationTypeKey(location) === 'location-scality-crr-v1',
152+
)
153+
.map((location) => location.name) || [];
154+
155+
// Custom resolver that preserves React Hook Form validation errors and runs Joi validation
156+
const customResolver: Resolver<Record<string, unknown>> = async (
157+
values,
158+
context,
159+
options,
160+
) => {
161+
// Check for existing React Hook Form validation errors from validate functions
162+
// These are stored in options.fields with their validation results
163+
const existingErrors: Record<string, Record<string, { type: string; message: string }>> = {};
164+
let hasExistingErrors = false;
165+
166+
// Check for CRR-related validation errors using the shared validation function
167+
const replication = values.replication as {
168+
destinationLocation?: string[];
169+
destinationBucketName?: string;
170+
destinationRole?: string;
171+
} | undefined;
172+
173+
if (replication) {
174+
const crrValidationErrors = validateCRRFields(
175+
replication.destinationLocation,
176+
crrLocationNames,
177+
replication.destinationBucketName,
178+
replication.destinationRole,
179+
);
180+
181+
if (crrValidationErrors.bucketNameError) {
182+
existingErrors.replication = {
183+
...existingErrors.replication,
184+
destinationBucketName: {
185+
type: 'validate',
186+
message: crrValidationErrors.bucketNameError,
187+
},
188+
};
189+
hasExistingErrors = true;
190+
}
191+
192+
if (crrValidationErrors.roleError) {
193+
existingErrors.replication = {
194+
...existingErrors.replication,
195+
destinationRole: {
196+
type: 'validate',
197+
message: crrValidationErrors.roleError,
198+
},
199+
};
200+
hasExistingErrors = true;
201+
}
202+
}
203+
204+
// If we have existing validation errors, run Joi validation but preserve our errors
205+
const bucketName = (values.replication as { sourceBucket?: string })?.sourceBucket;
206+
const streams = replicationsQuery.data ?? [];
207+
const unallowedBucketName = streams.flatMap((s) => {
208+
const { prefix, bucketName } = s.source;
209+
if (!prefix || prefix === '') return [bucketName];
210+
return [];
211+
});
212+
const prefixMandatory = !!streams.find((s) => {
213+
const { prefix } = s.source;
214+
return prefix && prefix !== '' && bucketName === s.source.bucketName;
215+
});
216+
const disPrefixes = disallowedPrefixes(bucketName, streams);
217+
218+
// Build Joi schema
219+
const schema = Joi.object({
220+
type: Joi.string().valid('replication', 'expiration', 'transition'),
221+
replication: Joi.when('type', {
222+
is: Joi.equal('replication'),
223+
then: Joi.object(
224+
replicationSchema(
225+
unallowedBucketName,
226+
disPrefixes,
227+
prefixMandatory,
228+
!!isTransient,
229+
),
230+
),
231+
otherwise: Joi.valid(),
232+
}),
233+
transition: Joi.when('type', {
234+
is: Joi.equal('transition'),
235+
then: Joi.object(transitionSchema),
236+
otherwise: Joi.valid(),
237+
}),
238+
expiration: Joi.when('type', {
239+
is: Joi.equal('expiration'),
240+
then: expirationSchema,
241+
otherwise: Joi.valid(),
242+
}),
243+
});
244+
245+
const joiValidator = joiResolver(schema);
246+
let joiResult;
247+
248+
if (['replication', 'transition'].includes(values.type as string)) {
249+
joiResult = await joiValidator(values, context, options as ResolverOptions<Record<string, unknown>>);
250+
} else {
251+
const expiration = prepareExpirationQuery(values.expiration as BucketWorkflowExpirationV1);
252+
joiResult = await joiValidator({ ...values, expiration }, context, options as ResolverOptions<Record<string, unknown>>);
253+
}
254+
255+
// Merge existing validation errors with Joi errors, giving priority to existing errors
256+
if (hasExistingErrors) {
257+
return {
258+
values: joiResult.values,
259+
errors: {
260+
...joiResult.errors,
261+
...existingErrors,
262+
},
263+
};
264+
}
265+
266+
return joiResult;
267+
};
268+
137269
const useFormMethods = useForm({
138270
mode: 'onChange',
139-
resolver: async (values, context, options) => {
140-
const bucketName = values.replication.sourceBucket;
141-
const streams = replicationsQuery.data ?? [];
142-
const unallowedBucketName = streams.flatMap((s) => {
143-
const { prefix, bucketName } = s.source;
144-
if (!prefix || prefix === '') return [bucketName];
145-
return [];
146-
});
147-
const prefixMandatory = !!streams.find((s) => {
148-
const { prefix } = s.source;
149-
return prefix && prefix !== '' && bucketName === s.source.bucketName;
150-
});
151-
const disPrefixes = disallowedPrefixes(bucketName, streams);
152-
const schema = Joi.object({
153-
type: Joi.string().valid('replication', 'expiration', 'transition'),
154-
replication: Joi.when('type', {
155-
is: Joi.equal('replication'),
156-
then: Joi.object(
157-
replicationSchema(
158-
unallowedBucketName,
159-
disPrefixes,
160-
prefixMandatory,
161-
!!isTransient,
162-
),
163-
),
164-
otherwise: Joi.valid(),
165-
}),
166-
transition: Joi.when('type', {
167-
is: Joi.equal('transition'),
168-
then: Joi.object(transitionSchema),
169-
otherwise: Joi.valid(),
170-
}),
171-
expiration: Joi.when('type', {
172-
is: Joi.equal('expiration'),
173-
then: expirationSchema,
174-
otherwise: Joi.valid(),
175-
}),
176-
});
177-
const joiValidator = joiResolver(schema);
178-
if (['replication', 'transition'].includes(values.type)) {
179-
const validation = await joiValidator(values, context, options);
180-
return validation;
181-
} else {
182-
const expiration = prepareExpirationQuery(values.expiration);
183-
return joiValidator({ ...values, expiration }, context, options);
184-
}
185-
},
271+
resolver: customResolver,
186272
defaultValues: defaultFormValues,
187273
});
188274

@@ -406,8 +492,8 @@ const CreateWorkflow = () => {
406492
<Select
407493
id="type"
408494
onBlur={onBlur}
409-
value={type}
410-
onChange={(value) => onChange(value)}
495+
value={type as string}
496+
onChange={(value) => onChange(value as string)}
411497
>
412498
<Select.Option
413499
value="replication"

0 commit comments

Comments
 (0)