Skip to content

Commit

Permalink
[8.x] [Security Solution] Allow users to save rule query with non cri…
Browse files Browse the repository at this point in the history
…tical validation errors (#202544) (#203233)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Allow users to save rule query with non critical
validation errors
(#202544)](#202544)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Maxim
Palenov","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-06T12:06:39Z","message":"[Security
Solution] Allow users to save rule query with non critical validation
errors (#202544)\n\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds functionality to allow users save EQL and
ES|QL queries in Prebuilt Rule Customization workflow by displaying a
confirmation modal with non critical validation errors (a.k.a warnings).
It also refactors confirmation modal usage in rule creation/editing
forms for better reusability.\r\n\r\n## Screenshots\r\n\r\n<img
width=\"1909\" alt=\"Screenshot 2024-12-03 at 14 01 36\"
src=\"https://github.com/user-attachments/assets/c0054fc1-b52b-400f-80d7-af42391f4e18\">\r\n\r\nhttps://github.com/user-attachments/assets/2a20fcfe-ffc0-4547-8621-7ac6873c8dc9\r\n\r\nhttps://github.com/user-attachments/assets/50b5cf5a-ea3f-4c22-a443-b5d4056a92c8\r\n\r\n<img
width=\"2541\" alt=\"Screenshot 2024-12-03 at 14 06 29\"
src=\"https://github.com/user-attachments/assets/dde3fd60-6c69-4f8e-a65a-837b2319e4ac\">\r\n\r\n<img
width=\"2552\" alt=\"Screenshot 2024-12-03 at 14 06 51\"
src=\"https://github.com/user-attachments/assets/220817a6-991f-4361-88d2-ee3a47a36ad6\">\r\n\r\n<img
width=\"2555\" alt=\"Screenshot 2024-12-03 at 14 07 52\"
src=\"https://github.com/user-attachments/assets/c46fc49c-9ce1-4472-bdea-f9507aa62ece\">\r\n\r\n<img
width=\"2553\" alt=\"Screenshot 2024-12-03 at 14 08 18\"
src=\"https://github.com/user-attachments/assets/92388c56-8644-4c54-8727-b9a73b3497d1\">","sha":"b0c7a8ce4f0ea528a7f96246e7f2a46d17f61d3f","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection
Rules","backport:version","v8.18.0"],"title":"[Security Solution] Allow
users to save rule query with non critical validation
errors","number":202544,"url":"https://github.com/elastic/kibana/pull/202544","mergeCommit":{"message":"[Security
Solution] Allow users to save rule query with non critical validation
errors (#202544)\n\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds functionality to allow users save EQL and
ES|QL queries in Prebuilt Rule Customization workflow by displaying a
confirmation modal with non critical validation errors (a.k.a warnings).
It also refactors confirmation modal usage in rule creation/editing
forms for better reusability.\r\n\r\n## Screenshots\r\n\r\n<img
width=\"1909\" alt=\"Screenshot 2024-12-03 at 14 01 36\"
src=\"https://github.com/user-attachments/assets/c0054fc1-b52b-400f-80d7-af42391f4e18\">\r\n\r\nhttps://github.com/user-attachments/assets/2a20fcfe-ffc0-4547-8621-7ac6873c8dc9\r\n\r\nhttps://github.com/user-attachments/assets/50b5cf5a-ea3f-4c22-a443-b5d4056a92c8\r\n\r\n<img
width=\"2541\" alt=\"Screenshot 2024-12-03 at 14 06 29\"
src=\"https://github.com/user-attachments/assets/dde3fd60-6c69-4f8e-a65a-837b2319e4ac\">\r\n\r\n<img
width=\"2552\" alt=\"Screenshot 2024-12-03 at 14 06 51\"
src=\"https://github.com/user-attachments/assets/220817a6-991f-4361-88d2-ee3a47a36ad6\">\r\n\r\n<img
width=\"2555\" alt=\"Screenshot 2024-12-03 at 14 07 52\"
src=\"https://github.com/user-attachments/assets/c46fc49c-9ce1-4472-bdea-f9507aa62ece\">\r\n\r\n<img
width=\"2553\" alt=\"Screenshot 2024-12-03 at 14 08 18\"
src=\"https://github.com/user-attachments/assets/92388c56-8644-4c54-8727-b9a73b3497d1\">","sha":"b0c7a8ce4f0ea528a7f96246e7f2a46d17f61d3f"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202544","number":202544,"mergeCommit":{"message":"[Security
Solution] Allow users to save rule query with non critical validation
errors (#202544)\n\n**Addresses:**
https://github.com/elastic/kibana/issues/171520\r\n\r\n##
Summary\r\n\r\nThis PR adds functionality to allow users save EQL and
ES|QL queries in Prebuilt Rule Customization workflow by displaying a
confirmation modal with non critical validation errors (a.k.a warnings).
It also refactors confirmation modal usage in rule creation/editing
forms for better reusability.\r\n\r\n## Screenshots\r\n\r\n<img
width=\"1909\" alt=\"Screenshot 2024-12-03 at 14 01 36\"
src=\"https://github.com/user-attachments/assets/c0054fc1-b52b-400f-80d7-af42391f4e18\">\r\n\r\nhttps://github.com/user-attachments/assets/2a20fcfe-ffc0-4547-8621-7ac6873c8dc9\r\n\r\nhttps://github.com/user-attachments/assets/50b5cf5a-ea3f-4c22-a443-b5d4056a92c8\r\n\r\n<img
width=\"2541\" alt=\"Screenshot 2024-12-03 at 14 06 29\"
src=\"https://github.com/user-attachments/assets/dde3fd60-6c69-4f8e-a65a-837b2319e4ac\">\r\n\r\n<img
width=\"2552\" alt=\"Screenshot 2024-12-03 at 14 06 51\"
src=\"https://github.com/user-attachments/assets/220817a6-991f-4361-88d2-ee3a47a36ad6\">\r\n\r\n<img
width=\"2555\" alt=\"Screenshot 2024-12-03 at 14 07 52\"
src=\"https://github.com/user-attachments/assets/c46fc49c-9ce1-4472-bdea-f9507aa62ece\">\r\n\r\n<img
width=\"2553\" alt=\"Screenshot 2024-12-03 at 14 08 18\"
src=\"https://github.com/user-attachments/assets/92388c56-8644-4c54-8727-b9a73b3497d1\">","sha":"b0c7a8ce4f0ea528a7f96246e7f2a46d17f61d3f"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Maxim Palenov <[email protected]>
  • Loading branch information
kibanamachine and maximpn authored Dec 6, 2024
1 parent 9cdf4d2 commit ba9ba62
Show file tree
Hide file tree
Showing 33 changed files with 827 additions and 708 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/
/x-pack/plugins/security_solution/public/common/components/with_hover_actions @elastic/security-threat-hunting-explore
/x-pack/plugins/security_solution/public/common/containers/matrix_histogram @elastic/security-threat-hunting-explore
/x-pack/plugins/security_solution/public/common/lib/cell_actions @elastic/security-threat-hunting-explore
/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warn @elastic/security-detection-rule-management
/x-pack/plugins/security_solution/public/cases @elastic/security-threat-hunting-explore
/x-pack/plugins/security_solution/public/explore @elastic/security-threat-hunting-explore
/x-pack/plugins/security_solution/public/overview @elastic/security-threat-hunting-explore
Expand Down
17 changes: 12 additions & 5 deletions x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,18 @@ interface Params {
signal?: AbortSignal;
}

export interface EqlResponseError {
code: EQL_ERROR_CODES;
messages?: string[];
error?: Error;
}
export type EqlResponseError =
| {
code:
| EQL_ERROR_CODES.INVALID_SYNTAX
| EQL_ERROR_CODES.INVALID_EQL
| EQL_ERROR_CODES.MISSING_DATA_SOURCE;
messages: string[];
}
| {
code: EQL_ERROR_CODES.FAILED_REQUEST;
error: Error;
};

export interface ValidateEqlResponse {
valid: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,44 @@
* 2.0.
*/

import React from 'react';

import React, { memo } from 'react';
import { EuiConfirmModal, EuiSpacer, EuiText } from '@elastic/eui';

import * as i18n from './translations';

interface SaveWithErrorsModalProps {
interface ConfirmValidationErrorsModalProps {
errors: string[];
onCancel: () => void;
onConfirm: () => void;
}

const SaveWithErrorsModalComponent = ({
export const ConfirmValidationErrorsModal = memo(function ConfirmValidationErrorsModal({
errors,
onCancel,
onConfirm,
}: SaveWithErrorsModalProps) => {
}: ConfirmValidationErrorsModalProps): JSX.Element {
return (
<EuiConfirmModal
data-test-subj="save-with-errors-confirmation-modal"
title={i18n.SAVE_WITH_ERRORS_MODAL_TITLE}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={i18n.SAVE_WITH_ERRORS_CANCEL_BUTTON}
confirmButtonText={i18n.SAVE_WITH_ERRORS_CONFIRM_BUTTON}
cancelButtonText={i18n.CANCEL}
confirmButtonText={i18n.CONFIRM}
defaultFocusedButton="confirm"
>
<>
{i18n.SAVE_WITH_ERRORS_MODAL_MESSAGE(errors.length)}
{i18n.SAVE_WITH_ERRORS_MESSAGE(errors.length)}
<EuiSpacer size="s" />
<ul>
{errors.map((validationError, idx) => {
{errors.map((error) => {
return (
<li key={idx}>
<EuiText>{validationError}</EuiText>
<li key={error}>
<EuiText>{error}</EuiText>
</li>
);
})}
</ul>
</>
</EuiConfirmModal>
);
};

export const SaveWithErrorsModal = React.memo(SaveWithErrorsModalComponent);
SaveWithErrorsModal.displayName = 'SaveWithErrorsModal';
});
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';
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 {errorsCount, plural, one {is} other {are}} {errorsCount} validation {errorsCount, plural, one {error} other {errors}} which can lead to failed rule executions, save anyway?',
values: { errorsCount },
});
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,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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';
import type { ValidationResults } from './validation_results';

export function extractValidationResults(
formFields: Readonly<FieldHook[]>,
warningValidationCodes: Readonly<string[]>
): ValidationResults {
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,
};
}
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, ValidationError } from '../../../shared_imports';

export interface FormHookWithWarnings<T extends FormData = FormData, I extends FormData = T>
extends FormHook<T, I> {
getValidationWarnings(): ValidationError[];
}
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_warnings';
export * from './use_form_with_warnings';
Loading

0 comments on commit ba9ba62

Please sign in to comment.