-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement non blocking validation errors as warnings
- Loading branch information
Showing
29 changed files
with
500 additions
and
699 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
...ugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
export * from './use_confirm_validation_errors_modal'; |
36 changes: 36 additions & 0 deletions
36
...ecurity_solution/public/common/hooks/use_confirm_validation_errors_modal/translations.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { i18n } from '@kbn/i18n'; | ||
|
||
export const SAVE_WITH_ERRORS_MODAL_TITLE = i18n.translate( | ||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.saveWithErrorsConfirmationModal.title', | ||
{ | ||
defaultMessage: 'There are validation errors', | ||
} | ||
); | ||
|
||
export const CANCEL = i18n.translate( | ||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.saveWithErrorsConfirmationModal.cancel', | ||
{ | ||
defaultMessage: 'Cancel', | ||
} | ||
); | ||
|
||
export const CONFIRM = i18n.translate( | ||
'xpack.securitySolution.detectionEngine.rules.upgradeRules.saveWithErrorsConfirmationModal.confirm', | ||
{ | ||
defaultMessage: 'Confirm', | ||
} | ||
); | ||
|
||
export const SAVE_WITH_ERRORS_MESSAGE = (errorsCount: number) => | ||
i18n.translate('xpack.securitySolution.detectionEngine.createRule.saveWithErrorsModalMessage', { | ||
defaultMessage: | ||
'There are {errorsCount} validation {errorsCount, plural, one {error} other {errors}} which can lead to failed rule executions, save anyway?', | ||
values: { errorsCount }, | ||
}); |
56 changes: 56 additions & 0 deletions
56
.../common/hooks/use_confirm_validation_errors_modal/use_confirm_validation_errors_modal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import type { ReactNode } from 'react'; | ||
import React, { useCallback, useState, useMemo } from 'react'; | ||
import { useBoolean } from '@kbn/react-hooks'; | ||
import { useAsyncConfirmation } from '../../../detection_engine/rule_management_ui/components/rules_table/rules_table/use_async_confirmation'; | ||
import { ConfirmValidationErrorsModal } from './confirm_validation_errors_modal'; | ||
|
||
interface UseFieldConfirmValidationErrorsModalResult { | ||
modal: ReactNode; | ||
confirmValidationErrors: (errorMessages: string[]) => Promise<boolean>; | ||
} | ||
|
||
export function useConfirmValidationErrorsModal(): UseFieldConfirmValidationErrorsModalResult { | ||
const [visible, { on: showModal, off: hideModal }] = useBoolean(false); | ||
const [initModal, confirm, cancel] = useAsyncConfirmation({ | ||
onInit: showModal, | ||
onFinish: hideModal, | ||
}); | ||
const [errorsToConfirm, setErrorsToConfirm] = useState<string[]>([]); | ||
|
||
const confirmValidationErrors = useCallback( | ||
(errorMessages: string[]) => { | ||
if (errorMessages.length === 0) { | ||
return Promise.resolve(true); | ||
} | ||
|
||
setErrorsToConfirm(errorMessages); | ||
|
||
return initModal(); | ||
}, | ||
[initModal, setErrorsToConfirm] | ||
); | ||
|
||
const modal = useMemo( | ||
() => | ||
visible ? ( | ||
<ConfirmValidationErrorsModal | ||
errors={errorsToConfirm} | ||
onConfirm={confirm} | ||
onCancel={cancel} | ||
/> | ||
) : null, | ||
[visible, errorsToConfirm, confirm, cancel] | ||
); | ||
|
||
return { | ||
modal, | ||
confirmValidationErrors, | ||
}; | ||
} |
39 changes: 39 additions & 0 deletions
39
...ns/security_solution/public/common/hooks/use_form_with_warn/extract_validation_results.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import type { FieldHook, ValidationError } from '../../../shared_imports'; | ||
|
||
interface ExtractValidationResultsResult { | ||
errors: ValidationError[]; | ||
warnings: ValidationError[]; | ||
} | ||
|
||
export function extractValidationResults( | ||
formFields: Readonly<FieldHook[]>, | ||
warningValidationCodes: Readonly<string[]> | ||
): ExtractValidationResultsResult { | ||
const warningValidationCodesSet = new Set(warningValidationCodes); | ||
const errors: ValidationError[] = []; | ||
const warnings: ValidationError[] = []; | ||
|
||
for (const field of formFields) { | ||
for (const error of field.errors) { | ||
const path = error.path ?? field.path; | ||
|
||
if (!error.code || !warningValidationCodesSet.has(error.code)) { | ||
errors.push({ ...error, path }); | ||
} else { | ||
warnings.push({ ...error, path }); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
errors, | ||
warnings, | ||
}; | ||
} |
13 changes: 13 additions & 0 deletions
13
...k/plugins/security_solution/public/common/hooks/use_form_with_warn/form_hook_with_warn.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import type { FormHook, FormData } from '../../../shared_imports'; | ||
|
||
export interface FormHookWithWarn<T extends FormData = FormData, I extends FormData = T> | ||
extends FormHook<T, I> { | ||
getWarnings(): string[]; | ||
} |
9 changes: 9 additions & 0 deletions
9
x-pack/plugins/security_solution/public/common/hooks/use_form_with_warn/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
export type * from './form_hook_with_warn'; | ||
export * from './use_form_with_warn'; |
125 changes: 125 additions & 0 deletions
125
...ck/plugins/security_solution/public/common/hooks/use_form_with_warn/use_form_with_warn.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||
import { isEmpty } from 'lodash'; | ||
import type { FormHook, ValidationError } from '../../../shared_imports'; | ||
import { useForm, type FormConfig, type FormData } from '../../../shared_imports'; | ||
import type { FormHookWithWarn } from './form_hook_with_warn'; | ||
import { extractValidationResults } from './extract_validation_results'; | ||
|
||
interface SubmitHandlerWithWarnExtras { | ||
errors: ValidationError[]; | ||
warnings: ValidationError[]; | ||
} | ||
|
||
export type FormWithWarnSubmitHandler<T extends FormData = FormData> = ( | ||
formData: T, | ||
isValid: boolean, | ||
extras: SubmitHandlerWithWarnExtras | ||
) => Promise<void>; | ||
|
||
interface FormWithWarnConfig<T extends FormData = FormData, I extends FormData = T> | ||
extends Omit<FormConfig<T, I>, 'onSubmit'> { | ||
onSubmit?: FormWithWarnSubmitHandler<T>; | ||
options: FormConfig['options'] & { | ||
warningValidationCodes: Readonly<string[]>; | ||
}; | ||
} | ||
|
||
interface UseFormWithWarnReturn<T extends FormData = FormData, I extends FormData = T> { | ||
form: FormHookWithWarn<T, I>; | ||
} | ||
|
||
export function useFormWithWarn<T extends FormData = FormData, I extends FormData = T>( | ||
formConfig: FormWithWarnConfig<T, I> | ||
): UseFormWithWarnReturn<T, I> { | ||
const { | ||
onSubmit, | ||
options: { warningValidationCodes }, | ||
} = formConfig; | ||
const { form } = useForm(formConfig as FormConfig<T, I>); | ||
const { validate: originalValidate, getFormData, getFields } = form; | ||
|
||
const errorsRef = useRef<ValidationError[]>([]); | ||
const warningsRef = useRef<ValidationError[]>([]); | ||
const [isSubmitted, setIsSubmitted] = useState(false); | ||
const [isSubmitting, setSubmitting] = useState(false); | ||
const [isValid, setIsValid] = useState<boolean>(); | ||
const isMounted = useRef(false); | ||
|
||
const validate: FormHook<T, I>['validate'] = useCallback(async () => { | ||
await originalValidate(); | ||
|
||
const validationResult = extractValidationResults( | ||
Object.values(getFields()), | ||
warningValidationCodes | ||
); | ||
|
||
errorsRef.current = validationResult.errors; | ||
warningsRef.current = validationResult.warnings; | ||
|
||
const isFormValid = isEmpty(errorsRef.current); | ||
|
||
setIsValid(isFormValid); | ||
|
||
return isFormValid; | ||
}, [originalValidate, getFields, warningValidationCodes, errorsRef, warningsRef]); | ||
|
||
const submit: FormHook<T, I>['submit'] = useCallback( | ||
async (e) => { | ||
if (e) { | ||
e.preventDefault(); | ||
} | ||
|
||
setIsSubmitted(true); | ||
setSubmitting(true); | ||
|
||
const isFormValid = await validate(); | ||
const formData = isFormValid ? getFormData() : ({} as T); | ||
|
||
if (onSubmit) { | ||
await onSubmit(formData, isFormValid, { | ||
errors: errorsRef.current, | ||
warnings: warningsRef.current, | ||
}); | ||
} | ||
|
||
if (isMounted.current) { | ||
setSubmitting(false); | ||
} | ||
|
||
return { data: formData, isValid: isFormValid }; | ||
}, | ||
[validate, getFormData, onSubmit, errorsRef, warningsRef] | ||
); | ||
|
||
// Track form's mounted state | ||
useEffect(() => { | ||
isMounted.current = true; | ||
|
||
return () => { | ||
isMounted.current = false; | ||
}; | ||
}, []); | ||
|
||
return useMemo( | ||
() => ({ | ||
form: { | ||
...form, | ||
isValid, | ||
isSubmitted, | ||
isSubmitting, | ||
validate, | ||
submit, | ||
getErrors: () => errorsRef.current.map((x) => x.message), | ||
getWarnings: () => warningsRef.current.map((x) => x.message), | ||
}, | ||
}), | ||
[form, validate, submit, isSubmitted, isSubmitting, isValid, errorsRef, warningsRef] | ||
); | ||
} |
Oops, something went wrong.