From dc77e8c149abc7a0bc2d9d3c65db0533ce45e4ff Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Fri, 6 Dec 2024 12:54:07 +0100
Subject: [PATCH 01/27] [Synthetics] Fix flaky tests for alerting default !!
(#203220)
## Summary
Fix flaky tests for alerting default, removed unecessary code.
I tested locally 30 times and it has been fixed now.
---
.../e2e/synthetics/journeys/alerting_default.journey.ts | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alerting_default.journey.ts b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alerting_default.journey.ts
index d2e495e0cc17a..43084f0e54d7b 100644
--- a/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alerting_default.journey.ts
+++ b/x-pack/plugins/observability_solution/synthetics/e2e/synthetics/journeys/alerting_default.journey.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { journey, step, expect, before, after } from '@elastic/synthetics';
+import { journey, step, before, after } from '@elastic/synthetics';
import { byTestId } from '../../helpers/utils';
import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';
import { cleanSettings } from './services/settings';
@@ -29,13 +29,10 @@ journey('AlertingDefaults', async ({ page, params }) => {
step('Go to Settings page', async () => {
await page.click('[aria-label="Toggle primary navigation"]');
await page.click('text=Synthetics');
- await page.click('text=Settings');
+ await page.getByTestId('settings-page-link').click();
});
step('Click text=Synthetics', async () => {
- expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/alerting');
- await page.click('.euiComboBox__inputWrap');
- await page.click("text=There aren't any options available");
await page.click('button:has-text("Add connector")');
await page.click('p:has-text("Slack")');
await page.click('input[type="text"]');
From b0c7a8ce4f0ea528a7f96246e7f2a46d17f61d3f Mon Sep 17 00:00:00 2001
From: Maxim Palenov
Date: Fri, 6 Dec 2024 13:06:39 +0100
Subject: [PATCH 02/27] [Security Solution] Allow users to save rule query with
non critical validation errors (#202544)
**Addresses:** https://github.com/elastic/kibana/issues/171520
## Summary
This 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.
## Screenshots
https://github.com/user-attachments/assets/2a20fcfe-ffc0-4547-8621-7ac6873c8dc9
https://github.com/user-attachments/assets/50b5cf5a-ea3f-4c22-a443-b5d4056a92c8
---
.github/CODEOWNERS | 1 +
.../public/common/hooks/eql/api.ts | 17 +-
.../confirm_validation_errors_modal.tsx} | 27 +-
.../index.tsx | 8 +
.../translations.tsx | 36 +++
.../use_confirm_validation_errors_modal.tsx | 56 ++++
.../extract_validation_results.ts | 35 ++
.../form_hook_with_warnings.ts | 13 +
.../hooks/use_form_with_warnings/index.ts | 9 +
.../use_form_with_warnings.test.tsx | 241 ++++++++++++++
.../use_form_with_warnings.ts | 154 +++++++++
.../validation_results.ts | 13 +
.../eql_query_edit/eql_query_bar.tsx | 23 +-
.../eql_query_edit/eql_query_edit.tsx | 59 ++--
.../components/eql_query_edit/validators.ts | 29 --
.../eql_query_validator_factory.ts | 19 +-
.../esql_query_edit/esql_query_edit.tsx | 10 +-
.../esql_query_validator_factory.ts | 9 -
.../constants/validation_warning_codes.ts | 38 +++
.../logic/extract_validation_messages.ts | 18 ++
.../translations.ts | 36 ---
.../rule_creation_ui/pages/form.test.ts | 302 ------------------
.../rule_creation_ui/pages/form.tsx | 105 +-----
.../pages/rule_creation/index.tsx | 148 ++++-----
.../pages/rule_editing/index.tsx | 71 ++--
.../rule_creation_ui/pages/translations.ts | 6 -
.../eql_query/eql_query_edit_adapter.tsx | 1 -
.../esql_query/esql_query_edit_adapter.tsx | 1 -
.../fields/rule_field_edit_form_wrapper.tsx | 37 ++-
.../public/shared_imports.ts | 1 +
.../translations/translations/fr-FR.json | 4 -
.../translations/translations/ja-JP.json | 4 -
.../translations/translations/zh-CN.json | 4 -
33 files changed, 827 insertions(+), 708 deletions(-)
rename x-pack/plugins/security_solution/public/{detection_engine/rule_creation_ui/components/save_with_errors_confirmation/index.tsx => common/hooks/use_confirm_validation_errors_modal/confirm_validation_errors_modal.tsx} (59%)
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/index.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/translations.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/use_confirm_validation_errors_modal.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/extract_validation_results.ts
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/form_hook_with_warnings.ts
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/index.ts
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.ts
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/validation_results.ts
delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts
rename x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/{ => validators}/eql_query_validator_factory.ts (78%)
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/constants/validation_warning_codes.ts
create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/extract_validation_messages.ts
delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 210ef172b78ea..5dbb563fda702 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2283,6 +2283,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
diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
index b586e0593ab6f..b26f935612755 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts
@@ -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;
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/index.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/confirm_validation_errors_modal.tsx
similarity index 59%
rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/index.tsx
rename to x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/confirm_validation_errors_modal.tsx
index 3f14945bedadc..52ef0465e39aa 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/confirm_validation_errors_modal.tsx
@@ -5,41 +5,39 @@
* 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 (
<>
- {i18n.SAVE_WITH_ERRORS_MODAL_MESSAGE(errors.length)}
+ {i18n.SAVE_WITH_ERRORS_MESSAGE(errors.length)}
- {errors.map((validationError, idx) => {
+ {errors.map((error) => {
return (
- -
- {validationError}
+
-
+ {error}
);
})}
@@ -47,7 +45,4 @@ const SaveWithErrorsModalComponent = ({
>
);
-};
-
-export const SaveWithErrorsModal = React.memo(SaveWithErrorsModalComponent);
-SaveWithErrorsModal.displayName = 'SaveWithErrorsModal';
+});
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/index.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/index.tsx
new file mode 100644
index 0000000000000..505422ad807ae
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/index.tsx
@@ -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';
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/translations.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/translations.tsx
new file mode 100644
index 0000000000000..de95f91e6ce73
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/translations.tsx
@@ -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 },
+ });
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/use_confirm_validation_errors_modal.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/use_confirm_validation_errors_modal.tsx
new file mode 100644
index 0000000000000..5dfdbfb969865
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_confirm_validation_errors_modal/use_confirm_validation_errors_modal.tsx
@@ -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;
+}
+
+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([]);
+
+ const confirmValidationErrors = useCallback(
+ (errorMessages: string[]) => {
+ if (errorMessages.length === 0) {
+ return Promise.resolve(true);
+ }
+
+ setErrorsToConfirm(errorMessages);
+
+ return initModal();
+ },
+ [initModal, setErrorsToConfirm]
+ );
+
+ const modal = useMemo(
+ () =>
+ visible ? (
+
+ ) : null,
+ [visible, errorsToConfirm, confirm, cancel]
+ );
+
+ return {
+ modal,
+ confirmValidationErrors,
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/extract_validation_results.ts b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/extract_validation_results.ts
new file mode 100644
index 0000000000000..b3de9f58f1afb
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/extract_validation_results.ts
@@ -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,
+ warningValidationCodes: Readonly
+): 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,
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/form_hook_with_warnings.ts b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/form_hook_with_warnings.ts
new file mode 100644
index 0000000000000..eb823cd19d25d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/form_hook_with_warnings.ts
@@ -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
+ extends FormHook {
+ getValidationWarnings(): ValidationError[];
+}
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/index.ts b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/index.ts
new file mode 100644
index 0000000000000..29e5c064b1a7a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/index.ts
@@ -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';
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.test.tsx
new file mode 100644
index 0000000000000..c9c9a939458e2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.test.tsx
@@ -0,0 +1,241 @@
+/*
+ * 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 React from 'react';
+import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components';
+import type { FieldConfig } from '../../../shared_imports';
+import { Form, UseField } from '../../../shared_imports';
+import type { FormWithWarningsSubmitHandler } from './use_form_with_warnings';
+import { useFormWithWarnings } from './use_form_with_warnings';
+
+describe('useFormWithWarn', () => {
+ describe('isValid', () => {
+ it('is `undefined` initially', async () => {
+ render();
+
+ expect(screen.getByText('isValid: "undefined"')).toBeInTheDocument();
+ });
+
+ it('is `true` when input is valid', async () => {
+ render();
+
+ typeText('someValue');
+ await submitForm();
+
+ await waitFor(() => {
+ expect(screen.getByText('isValid: true')).toBeInTheDocument();
+ });
+ });
+
+ it('is `true` when input has warnings', async () => {
+ render();
+
+ typeText('warning');
+ await submitForm();
+
+ expect(screen.getByText('isValid: true')).toBeInTheDocument();
+ });
+
+ it('is `false` when input has error', async () => {
+ render();
+
+ typeText('error');
+ await submitForm();
+
+ expect(screen.getByText('isValid: false')).toBeInTheDocument();
+ });
+ });
+
+ describe('isSubmitting', () => {
+ it('toggles upon form submission', async () => {
+ render();
+
+ expect(screen.getByText('isSubmitting: false')).toBeInTheDocument();
+
+ const finishAct = submitForm();
+
+ expect(screen.getByText('isSubmitting: true')).toBeInTheDocument();
+
+ await finishAct;
+ await waitFor(() => {
+ expect(screen.getByText('isSubmitting: false')).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('isSubmitted', () => {
+ it('switched to true after form submission', async () => {
+ render();
+
+ expect(screen.getByText('isSubmitted: false')).toBeInTheDocument();
+
+ await submitForm();
+
+ await waitFor(() => {
+ expect(screen.getByText('isSubmitted: true')).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('input w/o warnings', () => {
+ it('submits form successfully', async () => {
+ const handleSubmit = jest.fn();
+
+ render();
+ typeText('someValue');
+
+ await submitForm();
+
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalledWith({ testField: 'someValue' }, true, {
+ errors: [],
+ warnings: [],
+ });
+ });
+ });
+ });
+
+ describe('w/ warnings', () => {
+ it('submits form successfully', async () => {
+ const handleSubmit = jest.fn();
+
+ render();
+ typeText('warning');
+
+ await submitForm();
+
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalledWith({ testField: 'warning' }, true, {
+ errors: [],
+ warnings: [
+ expect.objectContaining({
+ code: 'warning',
+ message: 'Validation warning',
+ path: 'testField',
+ }),
+ ],
+ });
+ });
+ });
+ });
+
+ describe('w/ errors', () => {
+ it('passes validation errors to submit handler', async () => {
+ const handleSubmit = jest.fn();
+
+ render();
+ typeText('error');
+
+ await submitForm();
+
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalledWith({}, false, {
+ errors: [
+ expect.objectContaining({
+ code: 'error',
+ message: 'Validation error',
+ path: 'testField',
+ }),
+ ],
+ warnings: [],
+ });
+ });
+ });
+ });
+
+ describe('w/ errors and warnings', () => {
+ it('passes validation errors and warnings to submit handler', async () => {
+ const handleSubmit = jest.fn();
+
+ render();
+ typeText('error warning');
+
+ await submitForm();
+
+ await waitFor(() => {
+ expect(handleSubmit).toHaveBeenCalledWith({}, false, {
+ errors: [
+ expect.objectContaining({
+ code: 'error',
+ message: 'Validation error',
+ path: 'testField',
+ }),
+ ],
+ warnings: [],
+ });
+ });
+ });
+ });
+});
+
+interface TestFormProps {
+ onSubmit?: FormWithWarningsSubmitHandler;
+ warningValidationCodes: string[];
+}
+
+function TestForm({ onSubmit, warningValidationCodes }: TestFormProps): JSX.Element {
+ const { form } = useFormWithWarnings({
+ onSubmit,
+ options: {
+ warningValidationCodes,
+ },
+ });
+ const textFieldConfig: FieldConfig = {
+ validations: [
+ {
+ validator: (data) => {
+ if (data.value.includes('error')) {
+ return {
+ code: 'error',
+ message: 'Validation error',
+ };
+ }
+
+ if (data.value.includes('warning')) {
+ return {
+ code: 'warning',
+ message: 'Validation warning',
+ };
+ }
+ },
+ },
+ ],
+ };
+
+ return (
+
+ );
+}
+
+function submitForm(): Promise {
+ return act(async () => {
+ fireEvent.click(screen.getByText('Submit'));
+ });
+}
+
+function typeText(value: string): void {
+ act(() => {
+ fireEvent.input(screen.getByRole('textbox'), {
+ target: { value },
+ });
+ });
+}
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.ts b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.ts
new file mode 100644
index 0000000000000..191ba82f0943c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/use_form_with_warnings.ts
@@ -0,0 +1,154 @@
+/*
+ * 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 } from '../../../shared_imports';
+import { useForm, type FormConfig, type FormData } from '../../../shared_imports';
+import type { FormHookWithWarnings } from './form_hook_with_warnings';
+import { extractValidationResults } from './extract_validation_results';
+import type { ValidationResults } from './validation_results';
+
+export type FormWithWarningsSubmitHandler = (
+ formData: T,
+ isValid: boolean,
+ validationResults: ValidationResults
+) => Promise;
+
+interface FormWithWarningsConfig
+ extends Omit, 'onSubmit'> {
+ onSubmit?: FormWithWarningsSubmitHandler;
+ options: FormConfig['options'] & {
+ warningValidationCodes: Readonly;
+ };
+}
+
+interface UseFormWithWarningsReturn {
+ form: FormHookWithWarnings;
+}
+
+/**
+ * Form lib implements warning functionality via non blocking validators. `validations` allows to
+ * specify validation configuration with validator functions and extra parameters including
+ * `isBlocking`. Validators marked as `isBlocking` will produce non blocking validation errors
+ * a.k.a. warnings.
+ *
+ * The problem with the supported approach is lack of flexibility and necessary API like one for getting
+ * only blocking or non blocking errors. Flexibility requirement comes from complex async validators
+ * producing blocking and non blocking validation errors. There is no way to use `isBlocking` configuration
+ * option to separate errors. Separating such validating functions in two would lead to sending two
+ * HTTP requests and performing another async operations twice.
+ *
+ * On top of just having an ability to mark validation errors as non blocking via `isBlocking: false`
+ * configuration we require a way to return blocking and non blocking errors from a single validation
+ * function. It'd be possible by returning an error with `isBlocking` (or `isWarning`) flag along with
+ * `message` and `code` fields from a validator function. Attempts to reuse `__isBlocking__` internal
+ * field lead to inconsistent behavior.
+ *
+ * `useFormWithWarnings` implements warnings (non blocking errors) on top of `FormHook` using validation
+ * error codes as a flexible way to determine whether an error is a blocking error or it's a warning.
+ * It provides little interface extension to simplify errors and warnings consumption
+ *
+ * In some cases business logic requires implementing functionality to allow users perform an action
+ * despite non-critical validation errors a.k.a. warnings. Usually it's also required to inform users
+ * about warnings they got before proceeding for example via a modal.
+ *
+ * Since `FormHook` returned by `useForm` lacks of such functionality `useFormWithWarnings` is here to
+ * provide warnings functionality. It could be used and passed as `FormHook` when warnings functionality
+ * isn't required making absolutely transparent.
+ *
+ * **Important:** Validators use short circuiting by default. It means that any failed validation in
+ * `validations` configuration array will prevent the rest validators from running. When used with warnings
+ * it may lead to bugs when validator checks first for warnings. You have to make sure a value is validated
+ * for errors first and then for warnings.
+ *
+ * There is a ticket to move this functionality to Form lib https://github.com/elastic/kibana/issues/203097.
+ */
+export function useFormWithWarnings(
+ formConfig: FormWithWarningsConfig
+): UseFormWithWarningsReturn {
+ const {
+ onSubmit,
+ options: { warningValidationCodes },
+ } = formConfig;
+ const { form } = useForm(formConfig as FormConfig);
+ const { validate: originalValidate, getFormData, getFields } = form;
+
+ const validationResultsRef = useRef({
+ errors: [],
+ warnings: [],
+ });
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const [isSubmitting, setSubmitting] = useState(false);
+ const [isValid, setIsValid] = useState();
+ const isMounted = useRef(false);
+
+ const validate: FormHook['validate'] = useCallback(async () => {
+ await originalValidate();
+
+ validationResultsRef.current = extractValidationResults(
+ Object.values(getFields()),
+ warningValidationCodes
+ );
+
+ const isFormValid = isEmpty(validationResultsRef.current.errors);
+
+ setIsValid(isFormValid);
+
+ return isFormValid;
+ }, [originalValidate, getFields, warningValidationCodes, validationResultsRef]);
+
+ const submit: FormHook['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, validationResultsRef.current);
+ }
+
+ if (isMounted.current) {
+ setSubmitting(false);
+ }
+
+ return { data: formData, isValid: isFormValid };
+ },
+ [validate, getFormData, onSubmit, validationResultsRef]
+ );
+
+ // Track form's mounted state
+ useEffect(() => {
+ isMounted.current = true;
+
+ return () => {
+ isMounted.current = false;
+ };
+ }, []);
+
+ return useMemo(
+ () => ({
+ form: {
+ ...form,
+ isValid,
+ isSubmitted,
+ isSubmitting,
+ validate,
+ submit,
+ getErrors: () => validationResultsRef.current.errors.map((x) => x.message),
+ getValidationWarnings: () => validationResultsRef.current.warnings,
+ },
+ }),
+ [form, validate, submit, isSubmitted, isSubmitting, isValid, validationResultsRef]
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/validation_results.ts b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/validation_results.ts
new file mode 100644
index 0000000000000..238abab2cf53c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_form_with_warnings/validation_results.ts
@@ -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 { ValidationError } from '../../../shared_imports';
+
+export interface ValidationResults {
+ errors: ValidationError[];
+ warnings: ValidationError[];
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx
index c7ef28f5ed909..6111d2e1a2d3d 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx
@@ -6,7 +6,7 @@
*/
import type { FC, ChangeEvent } from 'react';
-import React, { useCallback, useEffect, useState, useRef } from 'react';
+import React, { useCallback, useEffect, useRef, useMemo } from 'react';
import { Subscription } from 'rxjs';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';
@@ -20,9 +20,9 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import type { EqlOptions } from '../../../../../common/search_strategy';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import { useKibana } from '../../../../common/lib/kibana';
+import { EQL_ERROR_CODES } from '../../../../common/hooks/eql/api';
import type { EqlQueryBarFooterProps } from './footer';
import { EqlQueryBarFooter } from './footer';
-import { getValidationResults } from './validators';
import * as i18n from './translations';
const TextArea = styled(EuiTextArea)`
@@ -81,12 +81,10 @@ export const EqlQueryBar: FC = ({
onValidatingChange,
}) => {
const { addError } = useAppToasts();
- const [errorMessages, setErrorMessages] = useState([]);
- const { isValidating, value: fieldValue, setValue: setFieldValue } = field;
- const { isValid, message, messages, error } = getValidationResults(field);
-
const { uiSettings } = useKibana().services;
const filterManager = useRef(new FilterManager(uiSettings));
+ const { isValidating, value: fieldValue, setValue: setFieldValue, isValid, errors } = field;
+ const errorMessages = useMemo(() => errors.map((x) => x.message), [errors]);
// Bubbles up field validity to parent.
// Using something like form `getErrors` does
@@ -98,14 +96,12 @@ export const EqlQueryBar: FC = ({
}, [isValid, onValidityChange]);
useEffect(() => {
- setErrorMessages(messages ?? []);
- }, [messages]);
+ const requestError = errors.find((x) => x.code === EQL_ERROR_CODES.FAILED_REQUEST);
- useEffect(() => {
- if (error) {
- addError(error, { title: i18n.EQL_VALIDATION_REQUEST_ERROR });
+ if (requestError) {
+ addError(requestError.message, { title: i18n.EQL_VALIDATION_REQUEST_ERROR });
}
- }, [error, addError]);
+ }, [errors, addError]);
useEffect(() => {
if (onValidatingChange) {
@@ -152,7 +148,6 @@ export const EqlQueryBar: FC = ({
if (onValidatingChange) {
onValidatingChange(true);
}
- setErrorMessages([]);
setFieldValue({
filters: fieldValue.filters,
query: {
@@ -182,7 +177,7 @@ export const EqlQueryBar: FC = ({
label={field.label}
labelAppend={field.labelAppend}
helpText={field.helpText}
- error={message}
+ error={errorMessages[0]}
isInvalid={!isValid && !isValidating}
fullWidth
data-test-subj={dataTestSubj}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx
index 75d3412705fde..36eef70b2be0f 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx
@@ -13,7 +13,7 @@ import { UseMultiFields } from '../../../../shared_imports';
import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory';
-import { eqlQueryValidatorFactory } from './eql_query_validator_factory';
+import { eqlQueryValidatorFactory } from './validators/eql_query_validator_factory';
import { EqlQueryBar } from './eql_query_bar';
import * as i18n from './translations';
@@ -28,8 +28,6 @@ interface EqlQueryEditProps {
required?: boolean;
loading?: boolean;
disabled?: boolean;
- // This is a temporal solution for Prebuilt Customization workflow
- skipEqlValidation?: boolean;
onValidityChange?: (arg: boolean) => void;
}
@@ -43,7 +41,6 @@ export function EqlQueryEdit({
required,
loading,
disabled,
- skipEqlValidation,
onValidityChange,
}: EqlQueryEditProps): JSX.Element {
const componentProps = useMemo(
@@ -73,43 +70,29 @@ export function EqlQueryEdit({
},
]
: []),
- ...(!skipEqlValidation
- ? [
- {
- validator: debounceAsync(
- (data: ValidationFuncArg) => {
- const { formData } = data;
- const eqlOptions =
- eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {};
+ {
+ validator: debounceAsync((data: ValidationFuncArg) => {
+ const { formData } = data;
+ const eqlOptions =
+ eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {};
- return eqlQueryValidatorFactory(
- dataView.id
- ? {
- dataViewId: dataView.id,
- eqlOptions,
- }
- : {
- indexPatterns: dataView.title.split(','),
- eqlOptions,
- }
- )(data);
- },
- 300
- ),
- },
- ]
- : []),
+ return eqlQueryValidatorFactory(
+ dataView.id
+ ? {
+ dataViewId: dataView.id,
+ eqlOptions,
+ }
+ : {
+ indexPatterns: dataView.title.split(','),
+ eqlOptions,
+ }
+ )(data);
+ }, 300),
+ isAsync: true,
+ },
],
}),
- [
- skipEqlValidation,
- eqlOptionsPath,
- required,
- dataView.id,
- dataView.title,
- path,
- fieldsToValidateOnChange,
- ]
+ [eqlOptionsPath, required, dataView.id, dataView.title, path, fieldsToValidateOnChange]
);
return (
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts
deleted file mode 100644
index 676a780d9daf5..0000000000000
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 } from '../../../../shared_imports';
-import { EQL_ERROR_CODES } from '../../../../common/hooks/eql/api';
-
-export const getValidationResults = (
- field: FieldHook
-): { isValid: boolean; message: string; messages?: string[]; error?: Error } => {
- const hasErrors = field.errors.length > 0;
- const isValid = !field.isChangingValue && !hasErrors;
-
- if (hasErrors) {
- const [error] = field.errors;
- const message = error.message;
-
- if (error.code === EQL_ERROR_CODES.FAILED_REQUEST) {
- return { isValid, message, error: error.error };
- } else {
- return { isValid, message, messages: error.messages };
- }
- } else {
- return { isValid, message: '' };
- }
-};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators/eql_query_validator_factory.ts
similarity index 78%
rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts
rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators/eql_query_validator_factory.ts
index 284d0670dfbc3..4de9e713f7f02 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators/eql_query_validator_factory.ts
@@ -6,13 +6,13 @@
*/
import { isEmpty } from 'lodash';
-import type { FormData, ValidationError, ValidationFunc } from '../../../../shared_imports';
-import { KibanaServices } from '../../../../common/lib/kibana';
-import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar_field';
-import type { EqlOptions } from '../../../../../common/search_strategy';
-import type { EqlResponseError } from '../../../../common/hooks/eql/api';
-import { EQL_ERROR_CODES, validateEql } from '../../../../common/hooks/eql/api';
-import { EQL_VALIDATION_REQUEST_ERROR } from './translations';
+import type { FormData, ValidationError, ValidationFunc } from '../../../../../shared_imports';
+import { KibanaServices } from '../../../../../common/lib/kibana';
+import type { FieldValueQueryBar } from '../../../../rule_creation_ui/components/query_bar_field';
+import type { EqlOptions } from '../../../../../../common/search_strategy';
+import type { EqlResponseError } from '../../../../../common/hooks/eql/api';
+import { EQL_ERROR_CODES, validateEql } from '../../../../../common/hooks/eql/api';
+import { EQL_VALIDATION_REQUEST_ERROR } from '../translations';
type EqlQueryValidatorFactoryParams =
| {
@@ -71,7 +71,7 @@ export function eqlQueryValidatorFactory({
function transformEqlResponseErrorToValidationError(
responseError: EqlResponseError
): ValidationError {
- if (responseError.error) {
+ if (responseError.code === EQL_ERROR_CODES.FAILED_REQUEST) {
return {
code: EQL_ERROR_CODES.FAILED_REQUEST,
message: EQL_VALIDATION_REQUEST_ERROR,
@@ -81,8 +81,7 @@ function transformEqlResponseErrorToValidationError(
return {
code: responseError.code,
- message: '',
- messages: responseError.messages,
+ message: responseError.messages.join(', '),
};
}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/esql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/esql_query_edit.tsx
index 695a3d121c9a6..b8ee292081d36 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/esql_query_edit.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/esql_query_edit.tsx
@@ -25,7 +25,6 @@ interface EsqlQueryEditProps {
required?: boolean;
loading?: boolean;
disabled?: boolean;
- skipIdColumnCheck?: boolean;
onValidityChange?: (arg: boolean) => void;
}
@@ -36,7 +35,6 @@ export const EsqlQueryEdit = memo(function EsqlQueryEdit({
required = false,
loading = false,
disabled = false,
- skipIdColumnCheck,
onValidityChange,
}: EsqlQueryEditProps): JSX.Element {
const queryClient = useQueryClient();
@@ -67,14 +65,12 @@ export const EsqlQueryEdit = memo(function EsqlQueryEdit({
]
: []),
{
- validator: debounceAsync(
- esqlQueryValidatorFactory({ queryClient, skipIdColumnCheck }),
- 300
- ),
+ validator: debounceAsync(esqlQueryValidatorFactory({ queryClient }), 300),
+ isAsync: true,
},
],
}),
- [required, path, fieldsToValidateOnChange, queryClient, skipIdColumnCheck]
+ [required, path, fieldsToValidateOnChange, queryClient]
);
return (
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/validators/esql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/validators/esql_query_validator_factory.ts
index c5b54db172a18..a0420f51586aa 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/validators/esql_query_validator_factory.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/esql_query_edit/validators/esql_query_validator_factory.ts
@@ -16,15 +16,10 @@ import * as i18n from './translations';
interface EsqlQueryValidatorFactoryParams {
queryClient: QueryClient;
- /**
- * This is a temporal fix to unlock prebuilt rule customization workflow
- */
- skipIdColumnCheck?: boolean;
}
export function esqlQueryValidatorFactory({
queryClient,
- skipIdColumnCheck,
}: EsqlQueryValidatorFactoryParams): ValidationFunc {
return async (...args) => {
const [{ value }] = args;
@@ -50,10 +45,6 @@ export function esqlQueryValidatorFactory({
};
}
- if (skipIdColumnCheck) {
- return;
- }
-
const columns = await fetchEsqlQueryColumns({
esqlQuery,
queryClient,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/constants/validation_warning_codes.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/constants/validation_warning_codes.ts
new file mode 100644
index 0000000000000..9593324a9c224
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/constants/validation_warning_codes.ts
@@ -0,0 +1,38 @@
+/*
+ * 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';
+import { EQL_ERROR_CODES } from '../../../common/hooks/eql/api';
+import { ESQL_ERROR_CODES } from '../components/esql_query_edit';
+
+const ESQL_FIELD_NAME = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.nonBlockingErrorCodes.esqlFieldName',
+ {
+ defaultMessage: 'ES|QL Query',
+ }
+);
+
+const EQL_FIELD_NAME = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.nonBlockingErrorCodes.eqlFieldName',
+ {
+ defaultMessage: 'EQL Query',
+ }
+);
+
+export const VALIDATION_WARNING_CODES = [
+ ESQL_ERROR_CODES.INVALID_ESQL,
+ EQL_ERROR_CODES.FAILED_REQUEST,
+ EQL_ERROR_CODES.INVALID_EQL,
+ EQL_ERROR_CODES.MISSING_DATA_SOURCE,
+] as const;
+
+export const VALIDATION_WARNING_CODE_FIELD_NAME_MAP: Readonly> = {
+ [ESQL_ERROR_CODES.INVALID_ESQL]: ESQL_FIELD_NAME,
+ [EQL_ERROR_CODES.FAILED_REQUEST]: EQL_FIELD_NAME,
+ [EQL_ERROR_CODES.INVALID_EQL]: EQL_FIELD_NAME,
+ [EQL_ERROR_CODES.MISSING_DATA_SOURCE]: EQL_FIELD_NAME,
+};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/extract_validation_messages.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/extract_validation_messages.ts
new file mode 100644
index 0000000000000..77847a0b12231
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/extract_validation_messages.ts
@@ -0,0 +1,18 @@
+/*
+ * 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 { capitalize } from 'lodash';
+import type { ValidationError } from '../../../shared_imports';
+
+export function extractValidationMessages(
+ validationErrors: ValidationError[],
+ errorCodeFieldNameMap: Readonly>
+): string[] {
+ return validationErrors.map(
+ (x) => `${errorCodeFieldNameMap[x.code ?? ''] ?? capitalize(x.path)}: ${x.message}`
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/translations.ts
deleted file mode 100644
index e470b06c7e829..0000000000000
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/save_with_errors_confirmation/translations.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.createRule.saveWithErrorsModalTitle',
- {
- defaultMessage: 'This rule has validation errors',
- }
-);
-
-export const SAVE_WITH_ERRORS_CANCEL_BUTTON = i18n.translate(
- 'xpack.securitySolution.detectionEngine.createRule.saveWithErrorsCancelButton',
- {
- defaultMessage: 'Cancel',
- }
-);
-
-export const SAVE_WITH_ERRORS_CONFIRM_BUTTON = i18n.translate(
- 'xpack.securitySolution.detectionEngine.createRule.saveWithErrorsConfirmButton',
- {
- defaultMessage: 'Confirm',
- }
-);
-
-export const SAVE_WITH_ERRORS_MODAL_MESSAGE = (errorsCount: number) =>
- i18n.translate('xpack.securitySolution.detectionEngine.createRule.saveWithErrorsModalMessage', {
- defaultMessage:
- 'This rule has {errorsCount} validation {errorsCount, plural, one {error} other {errors}} which can lead to failed rule executions, save anyway?',
- values: { errorsCount },
- });
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts
deleted file mode 100644
index 3210ac84b159a..0000000000000
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * 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 { renderHook } from '@testing-library/react-hooks';
-
-import type { FormData, FormHook, ValidationError } from '../../../shared_imports';
-import { EQL_ERROR_CODES } from '../../../common/hooks/eql/api';
-import type {
- AboutStepRule,
- ActionsStepRule,
- DefineStepRule,
- ScheduleStepRule,
-} from '../../../detections/pages/detection_engine/rules/types';
-import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../rule_creation/components/alert_suppression_edit';
-import { ESQL_ERROR_CODES } from '../../rule_creation/components/esql_query_edit';
-import { useRuleFormsErrors } from './form';
-
-const getFormWithErrorsMock = (fields: {
- [key: string]: { errors: Array> };
-}) => {
- return {
- getFields: () => fields,
- } as unknown as FormHook;
-};
-
-describe('useRuleFormsErrors', () => {
- describe('EQL query validation errors', () => {
- it('should return blocking error in case of syntax validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [
- {
- code: EQL_ERROR_CODES.INVALID_SYNTAX,
- message: '',
- messages: ["line 1:5: missing 'where' at 'demo'"],
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual(["line 1:5: missing 'where' at 'demo'"]);
- expect(nonBlockingErrors).toEqual([]);
- });
-
- it('should return non-blocking error in case of missing data source validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [
- {
- code: EQL_ERROR_CODES.MISSING_DATA_SOURCE,
- message: '',
- messages: [
- 'index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]',
- ],
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual([]);
- expect(nonBlockingErrors).toEqual([
- 'Query bar: index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]',
- ]);
- });
-
- it('should return non-blocking error in case of missing data field validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [
- {
- code: EQL_ERROR_CODES.INVALID_EQL,
- message: '',
- messages: [
- 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
- ],
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual([]);
- expect(nonBlockingErrors).toEqual([
- 'Query bar: Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]',
- ]);
- });
-
- it('should return non-blocking error in case of failed request error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [
- {
- code: EQL_ERROR_CODES.FAILED_REQUEST,
- message: 'An error occurred while validating your EQL query',
- error: new Error('Some internal error'),
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual([]);
- expect(nonBlockingErrors).toEqual([
- 'Query bar: An error occurred while validating your EQL query',
- ]);
- });
-
- it('should return blocking and non-blocking errors', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [
- {
- code: EQL_ERROR_CODES.MISSING_DATA_SOURCE,
- message: '',
- messages: ['Missing data source'],
- },
- ],
- },
- });
- const aboutStepForm = getFormWithErrorsMock({
- name: {
- errors: [
- {
- message: 'Required field',
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({
- defineStepForm,
- aboutStepForm,
- });
-
- expect(blockingErrors).toEqual(['Required field']);
- expect(nonBlockingErrors).toEqual(['Query bar: Missing data source']);
- });
- });
-
- describe('ES|QL query validation errors', () => {
- it('should return blocking error in case of syntax validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const validationError = {
- code: ESQL_ERROR_CODES.INVALID_SYNTAX,
- message: 'Broken ES|QL syntax',
- };
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [validationError],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual(['Broken ES|QL syntax']);
- expect(nonBlockingErrors).toEqual([]);
- });
-
- it('should return blocking error in case of missed ES|QL metadata validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const validationError = {
- code: ESQL_ERROR_CODES.ERR_MISSING_ID_FIELD_FROM_RESULT,
- message: 'Metadata is missing',
- };
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [validationError],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual(['Metadata is missing']);
- expect(nonBlockingErrors).toEqual([]);
- });
-
- it('should return non-blocking error in case of missing data field validation error', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const validationError = {
- code: ESQL_ERROR_CODES.INVALID_ESQL,
- message: 'Unknown column [hello.world]',
- };
- const defineStepForm = getFormWithErrorsMock({
- queryBar: {
- errors: [validationError],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
-
- expect(blockingErrors).toEqual([]);
- expect(nonBlockingErrors).toEqual(['Query bar: Unknown column [hello.world]']);
- });
- });
-
- describe('general cases', () => {
- it('should not return blocking and non-blocking errors in case there are none exist', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const defineStepForm = getFormWithErrorsMock({ queryBar: { errors: [] } });
- const aboutStepForm = getFormWithErrorsMock({ name: { errors: [] } });
- const scheduleStepForm = getFormWithErrorsMock({
- interval: { errors: [] },
- });
- const actionsStepForm = getFormWithErrorsMock({ actions: { errors: [] } });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({
- defineStepForm,
- aboutStepForm,
- scheduleStepForm,
- actionsStepForm,
- });
-
- expect(blockingErrors).toEqual([]);
- expect(nonBlockingErrors).toEqual([]);
- });
-
- it('should not return all errors', async () => {
- const { result } = renderHook(() => useRuleFormsErrors());
-
- const esqlValidationError = {
- code: ESQL_ERROR_CODES.INVALID_ESQL,
- message: 'Missing index [logs*]',
- };
- const groupByValidationError = {
- message: 'Number of grouping fields must be at most 3',
- };
-
- const defineStepForm = getFormWithErrorsMock({
- queryBar: { errors: [esqlValidationError] },
- [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: { errors: [groupByValidationError] },
- });
- const aboutStepForm = getFormWithErrorsMock({
- name: {
- errors: [
- {
- message: 'Required field',
- },
- ],
- },
- });
- const scheduleStepForm = getFormWithErrorsMock({
- interval: { errors: [] },
- });
- const actionsStepForm = getFormWithErrorsMock({
- actions: {
- errors: [
- {
- message: 'Missing webhook connector',
- },
- ],
- },
- });
-
- const { getRuleFormsErrors } = result.current;
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({
- defineStepForm,
- aboutStepForm,
- scheduleStepForm,
- actionsStepForm,
- });
-
- expect(blockingErrors).toEqual([
- 'Number of grouping fields must be at most 3',
- 'Required field',
- 'Missing webhook connector',
- ]);
- expect(nonBlockingErrors).toEqual(['Query bar: Missing index [logs*]']);
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx
index f88d0c1449442..65ff07924c70f 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx
@@ -5,8 +5,9 @@
* 2.0.
*/
-import { useState, useMemo, useEffect, useCallback } from 'react';
+import { useState, useMemo, useEffect } from 'react';
import type { DataViewBase } from '@kbn/es-query';
+import { useFormWithWarnings } from '../../../common/hooks/use_form_with_warnings';
import { isThreatMatchRule } from '../../../../common/detection_engine/utils';
import type {
AboutStepRule,
@@ -16,19 +17,17 @@ import type {
} from '../../../detections/pages/detection_engine/rules/types';
import { DataSourceType } from '../../../detections/pages/detection_engine/rules/types';
import { useKibana } from '../../../common/lib/kibana';
-import type { FormHook, ValidationError } from '../../../shared_imports';
-import { useForm, useFormData } from '../../../shared_imports';
+import type { FormHook } from '../../../shared_imports';
+import { useFormData } from '../../../shared_imports';
import { schema as defineRuleSchema } from '../components/step_define_rule/schema';
import {
schema as aboutRuleSchema,
threatMatchAboutSchema,
} from '../components/step_about_rule/schema';
-import { ESQL_ERROR_CODES } from '../../rule_creation/components/esql_query_edit';
import { schema as scheduleRuleSchema } from '../components/step_schedule_rule/schema';
import { getSchema as getActionsRuleSchema } from '../../rule_creation/components/step_rule_actions/get_schema';
import { useFetchIndex } from '../../../common/containers/source';
-import { EQL_ERROR_CODES } from '../../../common/hooks/eql/api';
-import * as i18n from './translations';
+import { VALIDATION_WARNING_CODES } from '../../rule_creation/constants/validation_warning_codes';
export interface UseRuleFormsProps {
defineStepDefault: DefineStepRule;
@@ -47,9 +46,9 @@ export const useRuleForms = ({
triggersActionsUi: { actionTypeRegistry },
} = useKibana().services;
// DEFINE STEP FORM
- const { form: defineStepForm } = useForm({
+ const { form: defineStepForm } = useFormWithWarnings({
defaultValue: defineStepDefault,
- options: { stripEmptyFields: false },
+ options: { stripEmptyFields: false, warningValidationCodes: VALIDATION_WARNING_CODES },
schema: defineRuleSchema,
});
const [defineStepFormData] = useFormData({
@@ -67,9 +66,9 @@ export const useRuleForms = ({
() => (isThreatMatchRule(defineStepData.ruleType) ? threatMatchAboutSchema : aboutRuleSchema),
[defineStepData.ruleType]
);
- const { form: aboutStepForm } = useForm({
+ const { form: aboutStepForm } = useFormWithWarnings({
defaultValue: aboutStepDefault,
- options: { stripEmptyFields: false },
+ options: { stripEmptyFields: false, warningValidationCodes: VALIDATION_WARNING_CODES },
schema: typeDependentAboutRuleSchema,
});
const [aboutStepFormData] = useFormData({
@@ -78,9 +77,9 @@ export const useRuleForms = ({
const aboutStepData = 'name' in aboutStepFormData ? aboutStepFormData : aboutStepDefault;
// SCHEDULE STEP FORM
- const { form: scheduleStepForm } = useForm({
+ const { form: scheduleStepForm } = useFormWithWarnings({
defaultValue: scheduleStepDefault,
- options: { stripEmptyFields: false },
+ options: { stripEmptyFields: false, warningValidationCodes: VALIDATION_WARNING_CODES },
schema: scheduleRuleSchema,
});
const [scheduleStepFormData] = useFormData({
@@ -91,9 +90,9 @@ export const useRuleForms = ({
// ACTIONS STEP FORM
const schema = useMemo(() => getActionsRuleSchema({ actionTypeRegistry }), [actionTypeRegistry]);
- const { form: actionsStepForm } = useForm({
+ const { form: actionsStepForm } = useFormWithWarnings({
defaultValue: actionsStepDefault,
- options: { stripEmptyFields: false },
+ options: { stripEmptyFields: false, warningValidationCodes: VALIDATION_WARNING_CODES },
schema,
});
const [actionsStepFormData] = useFormData({
@@ -158,81 +157,3 @@ export interface UseRuleFormsErrors {
scheduleStepForm?: FormHook;
actionsStepForm?: FormHook;
}
-
-const getFieldErrorMessages = (fieldError: ValidationError) => {
- if (fieldError.message.length > 0) {
- return [fieldError.message];
- } else if (Array.isArray(fieldError.messages)) {
- // EQL validation can return multiple errors and thus we store them in a custom `messages` field on `ValidationError` object.
- // Here we double check that `messages` is in fact an array and the content is of type `string`, otherwise we stringify it.
- return fieldError.messages.map((message) =>
- typeof message === 'string' ? message : JSON.stringify(message)
- );
- }
- return [];
-};
-
-const NON_BLOCKING_QUERY_BAR_ERROR_CODES = [
- ESQL_ERROR_CODES.INVALID_ESQL,
- EQL_ERROR_CODES.FAILED_REQUEST,
- EQL_ERROR_CODES.INVALID_EQL,
- EQL_ERROR_CODES.MISSING_DATA_SOURCE,
-];
-
-const isNonBlockingQueryBarErrorCode = (errorCode?: string) => {
- return !!NON_BLOCKING_QUERY_BAR_ERROR_CODES.find((code) => code === errorCode);
-};
-
-const NON_BLOCKING_ERROR_CODES = [...NON_BLOCKING_QUERY_BAR_ERROR_CODES];
-
-const isNonBlockingErrorCode = (errorCode?: string) => {
- return !!NON_BLOCKING_ERROR_CODES.find((code) => code === errorCode);
-};
-
-const transformValidationError = ({
- errorCode,
- errorMessage,
-}: {
- errorCode?: string;
- errorMessage: string;
-}) => {
- if (isNonBlockingQueryBarErrorCode(errorCode)) {
- return i18n.QUERY_BAR_VALIDATION_ERROR(errorMessage);
- }
- return errorMessage;
-};
-
-export const useRuleFormsErrors = () => {
- const getRuleFormsErrors = useCallback(
- ({ defineStepForm, aboutStepForm, scheduleStepForm, actionsStepForm }: UseRuleFormsErrors) => {
- const blockingErrors: string[] = [];
- const nonBlockingErrors: string[] = [];
-
- for (const [_, fieldHook] of Object.entries(defineStepForm?.getFields() ?? {})) {
- fieldHook.errors.forEach((fieldError) => {
- const messages = getFieldErrorMessages(fieldError);
- if (isNonBlockingErrorCode(fieldError.code)) {
- nonBlockingErrors.push(
- ...messages.map((message) =>
- transformValidationError({ errorCode: fieldError.code, errorMessage: message })
- )
- );
- } else {
- blockingErrors.push(...messages);
- }
- });
- }
-
- const blockingForms = [aboutStepForm, scheduleStepForm, actionsStepForm];
- blockingForms.forEach((form) => {
- for (const [_, fieldHook] of Object.entries(form?.getFields() ?? {})) {
- blockingErrors.push(...fieldHook.errors.map((fieldError) => fieldError.message));
- }
- });
- return { blockingErrors, nonBlockingErrors };
- },
- []
- );
-
- return { getRuleFormsErrors };
-};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx
index 6019b696a089c..4bf634595a9db 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx
@@ -56,6 +56,8 @@ import {
} from '../../../../detections/pages/detection_engine/rules/helpers';
import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types';
import { RuleStep } from '../../../../detections/pages/detection_engine/rules/types';
+import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../../rule_creation/components/alert_suppression_edit';
+import { useConfirmValidationErrorsModal } from '../../../../common/hooks/use_confirm_validation_errors_modal';
import { formatRule } from './helpers';
import { useEsqlIndex, useEsqlQueryForAboutStep } from '../../hooks';
import * as i18n from './translations';
@@ -77,11 +79,11 @@ import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana';
import { RulePreview } from '../../components/rule_preview';
import { getIsRulePreviewDisabled } from '../../components/rule_preview/helpers';
import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs';
+import { VALIDATION_WARNING_CODE_FIELD_NAME_MAP } from '../../../rule_creation/constants/validation_warning_codes';
+import { extractValidationMessages } from '../../../rule_creation/logic/extract_validation_messages';
import { NextStep } from '../../components/next_step';
-import { useRuleForms, useRuleFormsErrors, useRuleIndexPattern } from '../form';
+import { useRuleForms, useRuleIndexPattern } from '../form';
import { CustomHeaderPageMemo } from '..';
-import { SaveWithErrorsModal } from '../../components/save_with_errors_confirmation';
-import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../../rule_creation/components/alert_suppression_edit';
const MyEuiPanel = styled(EuiPanel)<{
zindex?: number;
@@ -178,6 +180,9 @@ const CreateRulePageComponent: React.FC = () => {
actionsStepDefault,
});
+ const { modal: confirmSavingWithWarningModal, confirmValidationErrors } =
+ useConfirmValidationErrorsModal();
+
const isThreatMatchRuleValue = useMemo(
() => isThreatMatchRule(defineStepData.ruleType),
[defineStepData.ruleType]
@@ -203,12 +208,6 @@ const CreateRulePageComponent: React.FC = () => {
const [isQueryBarValid, setIsQueryBarValid] = useState(false);
const [isThreatQueryBarValid, setIsThreatQueryBarValid] = useState(false);
- const [isSaveWithErrorsModalVisible, setIsSaveWithErrorsModalVisible] = useState(false);
- const [enableRuleAfterConfirmation, setEnableRuleAfterConfirmation] = useState(false);
- const [nonBlockingRuleErrors, setNonBlockingRuleErrors] = useState([]);
-
- const { getRuleFormsErrors } = useRuleFormsErrors();
-
const esqlQueryForAboutStep = useEsqlQueryForAboutStep({ defineStepData, activeStep });
const esqlIndex = useEsqlIndex(defineStepData.queryBar.query.query, ruleType);
@@ -315,73 +314,73 @@ const CreateRulePageComponent: React.FC = () => {
switch (step) {
case RuleStep.defineRule: {
const valid = await defineStepForm.validate();
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ defineStepForm });
- return { valid, blockingErrors, nonBlockingErrors };
+
+ return {
+ valid,
+ warnings: defineStepForm.getValidationWarnings(),
+ };
}
+
case RuleStep.aboutRule: {
const valid = await aboutStepForm.validate();
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ aboutStepForm });
- return { valid, blockingErrors, nonBlockingErrors };
+
+ return {
+ valid,
+ warnings: aboutStepForm.getValidationWarnings(),
+ };
}
case RuleStep.scheduleRule: {
const valid = await scheduleStepForm.validate();
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ scheduleStepForm });
- return { valid, blockingErrors, nonBlockingErrors };
+
+ return {
+ valid,
+ warnings: scheduleStepForm.getValidationWarnings(),
+ };
}
case RuleStep.ruleActions: {
const valid = await actionsStepForm.validate();
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({ actionsStepForm });
- return { valid, blockingErrors, nonBlockingErrors };
+
+ return {
+ valid,
+ warnings: actionsStepForm.getValidationWarnings(),
+ };
}
}
},
- [aboutStepForm, actionsStepForm, defineStepForm, getRuleFormsErrors, scheduleStepForm]
- );
-
- const validateEachStep = useCallback(async () => {
- const {
- valid: defineStepFormValid,
- blockingErrors: defineStepBlockingErrors,
- nonBlockingErrors: defineStepNonBlockingErrors,
- } = await validateStep(RuleStep.defineRule);
- const {
- valid: aboutStepFormValid,
- blockingErrors: aboutStepBlockingErrors,
- nonBlockingErrors: aboutStepNonBlockingErrors,
- } = await validateStep(RuleStep.aboutRule);
- const {
- valid: scheduleStepFormValid,
- blockingErrors: scheduleStepBlockingErrors,
- nonBlockingErrors: scheduleStepNonBlockingErrors,
- } = await validateStep(RuleStep.scheduleRule);
- const {
- valid: actionsStepFormValid,
- blockingErrors: actionsStepBlockingErrors,
- nonBlockingErrors: actionsStepNonBlockingErrors,
- } = await validateStep(RuleStep.ruleActions);
+ [aboutStepForm, actionsStepForm, defineStepForm, scheduleStepForm]
+ );
+
+ const validateAllSteps = useCallback(async () => {
+ const { valid: defineStepFormValid, warnings: defineStepWarnings } = await validateStep(
+ RuleStep.defineRule
+ );
+ const { valid: aboutStepFormValid, warnings: aboutStepWarnings } = await validateStep(
+ RuleStep.aboutRule
+ );
+ const { valid: scheduleStepFormValid, warnings: scheduleStepWarnings } = await validateStep(
+ RuleStep.scheduleRule
+ );
+ const { valid: actionsStepFormValid, warnings: actionsStepWarnings } = await validateStep(
+ RuleStep.ruleActions
+ );
const valid =
defineStepFormValid && aboutStepFormValid && scheduleStepFormValid && actionsStepFormValid;
- const blockingErrors = [
- ...defineStepBlockingErrors,
- ...aboutStepBlockingErrors,
- ...scheduleStepBlockingErrors,
- ...actionsStepBlockingErrors,
- ];
- const nonBlockingErrors = [
- ...defineStepNonBlockingErrors,
- ...aboutStepNonBlockingErrors,
- ...scheduleStepNonBlockingErrors,
- ...actionsStepNonBlockingErrors,
+ const warnings = [
+ ...defineStepWarnings,
+ ...aboutStepWarnings,
+ ...scheduleStepWarnings,
+ ...actionsStepWarnings,
];
- return { valid, blockingErrors, nonBlockingErrors };
+ return { valid, warnings };
}, [validateStep]);
const editStep = useCallback(
async (step: RuleStep) => {
- const { valid, blockingErrors } = await validateStep(activeStep);
- if (valid || !blockingErrors.length) {
+ const { valid } = await validateStep(activeStep);
+
+ if (valid) {
goToStep(step);
}
},
@@ -440,34 +439,21 @@ const CreateRulePageComponent: React.FC = () => {
]
);
- const showSaveWithErrorsModal = useCallback(() => setIsSaveWithErrorsModalVisible(true), []);
- const closeSaveWithErrorsModal = useCallback(() => setIsSaveWithErrorsModalVisible(false), []);
- const onConfirmSaveWithErrors = useCallback(async () => {
- closeSaveWithErrorsModal();
- await createRuleFromFormData(enableRuleAfterConfirmation);
- }, [closeSaveWithErrorsModal, createRuleFromFormData, enableRuleAfterConfirmation]);
-
const submitRule = useCallback(
async (enabled: boolean) => {
- const { valid, blockingErrors, nonBlockingErrors } = await validateEachStep();
- if (valid) {
- // There are no validation errors, thus proceed to rule creation
- await createRuleFromFormData(enabled);
- return;
- }
+ const { valid, warnings } = await validateAllSteps();
+ const warningMessages = extractValidationMessages(
+ warnings,
+ VALIDATION_WARNING_CODE_FIELD_NAME_MAP
+ );
- if (blockingErrors.length > 0) {
- // There are blocking validation errors, thus do not allow user to create a rule
+ if (!valid || !(await confirmValidationErrors(warningMessages))) {
return;
}
- if (nonBlockingErrors.length > 0) {
- // There are non-blocking validation errors, thus confirm that user understand that this can cause rule failures
- setEnableRuleAfterConfirmation(enabled);
- setNonBlockingRuleErrors(nonBlockingErrors);
- showSaveWithErrorsModal();
- }
+
+ await createRuleFromFormData(enabled);
},
- [createRuleFromFormData, showSaveWithErrorsModal, validateEachStep]
+ [createRuleFromFormData, validateAllSteps, confirmValidationErrors]
);
const defineRuleButtonType =
@@ -846,13 +832,7 @@ const CreateRulePageComponent: React.FC = () => {
return (
<>
- {isSaveWithErrorsModalVisible && (
-
- )}
+ {confirmSavingWithWarningModal}
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
index 594b9d2a35598..3327e45bd2bb7 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx
@@ -21,6 +21,7 @@ import type { FC } from 'react';
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
+import { useConfirmValidationErrorsModal } from '../../../../common/hooks/use_confirm_validation_errors_modal';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { isEsqlRule } from '../../../../../common/detection_engine/utils';
import { RulePreview } from '../../components/rule_preview';
@@ -67,10 +68,11 @@ import {
import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction';
import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions';
import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query';
-import { useRuleForms, useRuleFormsErrors, useRuleIndexPattern } from '../form';
+import { extractValidationMessages } from '../../../rule_creation/logic/extract_validation_messages';
+import { VALIDATION_WARNING_CODE_FIELD_NAME_MAP } from '../../../rule_creation/constants/validation_warning_codes';
+import { useRuleForms, useRuleIndexPattern } from '../form';
import { useEsqlIndex, useEsqlQueryForAboutStep } from '../../hooks';
import { CustomHeaderPageMemo } from '..';
-import { SaveWithErrorsModal } from '../../components/save_with_errors_confirmation';
import { useIsPrebuiltRulesCustomizationEnabled } from '../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled';
import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../../rule_creation/components/alert_suppression_edit';
@@ -104,9 +106,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
const [isQueryBarValid, setIsQueryBarValid] = useState(false);
const [isThreatQueryBarValid, setIsThreatQueryBarValid] = useState(false);
- const [isSaveWithErrorsModalVisible, setIsSaveWithErrorsModalVisible] = useState(false);
- const [nonBlockingRuleErrors, setNonBlockingRuleErrors] = useState([]);
-
const backOptions = useMemo(
() => ({
path: getRuleDetailsUrl(ruleId ?? ''),
@@ -140,7 +139,8 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
actionsStepDefault: ruleActionsData,
});
- const { getRuleFormsErrors } = useRuleFormsErrors();
+ const { modal: confirmSavingWithWarningModal, confirmValidationErrors } =
+ useConfirmValidationErrorsModal();
const esqlQueryForAboutStep = useEsqlQueryForAboutStep({ defineStepData, activeStep });
@@ -411,16 +411,7 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
updateRule,
]);
- const showSaveWithErrorsModal = useCallback(() => setIsSaveWithErrorsModalVisible(true), []);
- const closeSaveWithErrorsModal = useCallback(() => setIsSaveWithErrorsModalVisible(false), []);
- const onConfirmSaveWithErrors = useCallback(async () => {
- closeSaveWithErrorsModal();
- await saveChanges();
- }, [closeSaveWithErrorsModal, saveChanges]);
-
const onSubmit = useCallback(async () => {
- setNonBlockingRuleErrors([]);
-
const actionsStepFormValid = await actionsStepForm.validate();
if (!isPrebuiltRulesCustomizationEnabled && rule.immutable) {
// Since users cannot edit Define, About and Schedule tabs of the rule, we skip validation of those to avoid
@@ -435,29 +426,36 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
const defineStepFormValid = await defineStepForm.validate();
const aboutStepFormValid = await aboutStepForm.validate();
const scheduleStepFormValid = await scheduleStepForm.validate();
+
if (
- defineStepFormValid &&
- aboutStepFormValid &&
- scheduleStepFormValid &&
- actionsStepFormValid
+ !defineStepFormValid ||
+ !aboutStepFormValid ||
+ !scheduleStepFormValid ||
+ !actionsStepFormValid
) {
- await saveChanges();
return;
}
- const { blockingErrors, nonBlockingErrors } = getRuleFormsErrors({
- defineStepForm,
- aboutStepForm,
- scheduleStepForm,
- actionsStepForm,
- });
- if (blockingErrors.length > 0) {
+ const defineRuleWarnings = defineStepForm.getValidationWarnings();
+ const aboutRuleWarnings = aboutStepForm.getValidationWarnings();
+ const scheduleRuleWarnings = scheduleStepForm.getValidationWarnings();
+ const ruleActionsWarnings = actionsStepForm.getValidationWarnings();
+
+ const warnings = extractValidationMessages(
+ [
+ ...defineRuleWarnings,
+ ...aboutRuleWarnings,
+ ...scheduleRuleWarnings,
+ ...ruleActionsWarnings,
+ ],
+ VALIDATION_WARNING_CODE_FIELD_NAME_MAP
+ );
+
+ if (!(await confirmValidationErrors(warnings))) {
return;
}
- if (nonBlockingErrors.length > 0) {
- setNonBlockingRuleErrors(nonBlockingErrors);
- showSaveWithErrorsModal();
- }
+
+ await saveChanges();
}, [
actionsStepForm,
isPrebuiltRulesCustomizationEnabled,
@@ -465,9 +463,8 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
defineStepForm,
aboutStepForm,
scheduleStepForm,
- getRuleFormsErrors,
+ confirmValidationErrors,
saveChanges,
- showSaveWithErrorsModal,
]);
const onTabClick = useCallback(async (tab: EuiTabbedContentTab) => {
@@ -523,13 +520,7 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
return (
<>
- {isSaveWithErrorsModalVisible && (
-
- )}
+ {confirmSavingWithWarningModal}
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/translations.ts
index 77ea9438f66dc..e602b8be712c2 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/translations.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/translations.ts
@@ -13,9 +13,3 @@ export const RULE_PREVIEW_TITLE = i18n.translate(
defaultMessage: 'Rule preview',
}
);
-
-export const QUERY_BAR_VALIDATION_ERROR = (validationError: string) =>
- i18n.translate('xpack.securitySolution.detectionEngine.createRule.validationError', {
- values: { validationError },
- defaultMessage: 'Query bar: {validationError}',
- });
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx
index 787891452f1d7..cea9b9308c0df 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx
@@ -29,7 +29,6 @@ export function EqlQueryEditAdapter({
dataView={dataView ?? DEFAULT_DATA_VIEW_BASE}
loading={isLoading}
disabled={isLoading}
- skipEqlValidation
/>
);
}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/esql_query/esql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/esql_query/esql_query_edit_adapter.tsx
index a9375b7316bb3..faf43d5b88b22 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/esql_query/esql_query_edit_adapter.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/esql_query/esql_query_edit_adapter.tsx
@@ -23,7 +23,6 @@ export function EsqlQueryEditAdapter({
dataView={dataView ?? DEFAULT_DATA_VIEW_BASE}
loading={isLoading}
disabled={isLoading}
- skipIdColumnCheck
/>
);
}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx
index 1b45bea28880f..a5f7eedc6114c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_field_edit_form_wrapper.tsx
@@ -7,7 +7,10 @@
import React, { useCallback, useEffect } from 'react';
import { EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui';
-import { useForm, Form } from '../../../../../../../shared_imports';
+import { extractValidationMessages } from '../../../../../../rule_creation/logic/extract_validation_messages';
+import type { FormWithWarningsSubmitHandler } from '../../../../../../../common/hooks/use_form_with_warnings';
+import { useFormWithWarnings } from '../../../../../../../common/hooks/use_form_with_warnings';
+import { Form } from '../../../../../../../shared_imports';
import type { FormSchema, FormData } from '../../../../../../../shared_imports';
import type {
DiffableAllFields,
@@ -17,6 +20,11 @@ import { useFinalSideContext } from '../../final_side/final_side_context';
import { useDiffableRuleContext } from '../../diffable_rule_context';
import * as i18n from '../../translations';
import type { RuleFieldEditComponentProps } from './rule_field_edit_component_props';
+import { useConfirmValidationErrorsModal } from '../../../../../../../common/hooks/use_confirm_validation_errors_modal';
+import {
+ VALIDATION_WARNING_CODE_FIELD_NAME_MAP,
+ VALIDATION_WARNING_CODES,
+} from '../../../../../../rule_creation/constants/validation_warning_codes';
type RuleFieldEditComponent = React.ComponentType;
@@ -56,9 +64,16 @@ export function RuleFieldEditFormWrapper({
[deserializer, finalDiffableRule]
);
- const handleSubmit = useCallback(
- async (formData: FormData, isValid: boolean) => {
- if (!isValid) {
+ const { modal, confirmValidationErrors } = useConfirmValidationErrorsModal();
+
+ const handleSubmit = useCallback(
+ async (formData: FormData, isValid: boolean, { warnings }) => {
+ const warningMessages = extractValidationMessages(
+ warnings,
+ VALIDATION_WARNING_CODE_FIELD_NAME_MAP
+ );
+
+ if (!isValid || !(await confirmValidationErrors(warningMessages))) {
return;
}
@@ -69,15 +84,24 @@ export function RuleFieldEditFormWrapper({
});
setReadOnlyMode();
},
- [fieldName, finalDiffableRule.rule_id, setReadOnlyMode, setRuleFieldResolvedValue]
+ [
+ confirmValidationErrors,
+ fieldName,
+ finalDiffableRule.rule_id,
+ setReadOnlyMode,
+ setRuleFieldResolvedValue,
+ ]
);
- const { form } = useForm({
+ const { form } = useFormWithWarnings({
schema: ruleFieldFormSchema,
defaultValue: getDefaultValue(fieldName, finalDiffableRule),
deserializer: deserialize,
serializer,
onSubmit: handleSubmit,
+ options: {
+ warningValidationCodes: VALIDATION_WARNING_CODES,
+ },
});
// form.isValid has `undefined` value until all fields are dirty.
@@ -96,6 +120,7 @@ export function RuleFieldEditFormWrapper({
{i18n.SAVE_BUTTON_LABEL}
+ {modal}
),
nullOptionLabel: i18n.translate(
@@ -136,7 +149,12 @@ export const UserFilterPanel: FC<{}> = () => {
}
),
nullOptionProps: {
- append: ,
+ append: (
+
+ ),
},
clearButtonLabel: (
{}) =>
getTagIdsFromReferences: () => [],
isTaggingEnabled: () => true,
isFavoritesEnabled: () => false,
+ isKibanaVersioningEnabled: false,
...params,
};
diff --git a/packages/content-management/table_list_view_table/src/services.tsx b/packages/content-management/table_list_view_table/src/services.tsx
index a8c3d3cc9f60b..9db14069107e8 100644
--- a/packages/content-management/table_list_view_table/src/services.tsx
+++ b/packages/content-management/table_list_view_table/src/services.tsx
@@ -79,6 +79,9 @@ export interface Services {
/** Handler to return the url to navigate to the kibana tags management */
getTagManagementUrl: () => string;
getTagIdsFromReferences: (references: SavedObjectsReference[]) => string[];
+ /** Whether versioning is enabled for the current kibana instance. (aka is Serverless)
+ This is used to determine if we should show the version mentions in the help text.*/
+ isKibanaVersioningEnabled: boolean;
}
const TableListViewContext = React.createContext(null);
@@ -185,6 +188,12 @@ export interface TableListViewKibanaDependencies {
* Content insights client to enable content insights features.
*/
contentInsightsClient?: ContentInsightsClientPublic;
+
+ /**
+ * Flag to indicate if Kibana versioning is enabled. (aka not Serverless)
+ * Used to determine if we should show the version mentions in the help text.
+ */
+ isKibanaVersioningEnabled?: boolean;
}
/**
@@ -251,7 +260,10 @@ export const TableListViewKibanaProvider: FC<
-
+
{
@@ -282,6 +294,7 @@ export const TableListViewKibanaProvider: FC<
itemHasTags={itemHasTags}
getTagIdsFromReferences={getTagIdsFromReferences}
getTagManagementUrl={() => core.http.basePath.prepend(TAG_MANAGEMENT_APP_URL)}
+ isKibanaVersioningEnabled={services.isKibanaVersioningEnabled ?? false}
>
{children}
diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
index c7653c668f0df..011f00256d979 100644
--- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
+++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
@@ -376,6 +376,7 @@ function TableListViewTableComp({
DateFormatterComp,
getTagList,
isFavoritesEnabled,
+ isKibanaVersioningEnabled,
} = useServices();
const openContentEditor = useOpenContentEditor();
@@ -578,7 +579,7 @@ function TableListViewTableComp({
appendRows: contentInsightsServices && (
// have to "REWRAP" in the provider here because it will be rendered in a different context
-
+
),
});
@@ -591,6 +592,7 @@ function TableListViewTableComp({
tableItemsRowActions,
fetchItems,
contentInsightsServices,
+ entityNamePlural,
]
);
@@ -646,7 +648,7 @@ function TableListViewTableComp({
) : record.managed ? (
) : (
-
+
),
sortable:
false /* createdBy column is not sortable because it doesn't make sense to sort by id*/,
@@ -753,6 +755,7 @@ function TableListViewTableComp({
inspectItem,
entityName,
isFavoritesEnabled,
+ isKibanaVersioningEnabled,
]);
const itemsById = useMemo(() => {
diff --git a/packages/content-management/user_profiles/src/components/user_missing_tip.tsx b/packages/content-management/user_profiles/src/components/user_missing_tip.tsx
index 602e9cc228975..08612e731f816 100644
--- a/packages/content-management/user_profiles/src/components/user_missing_tip.tsx
+++ b/packages/content-management/user_profiles/src/components/user_missing_tip.tsx
@@ -7,29 +7,67 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiIconTip, IconType } from '@elastic/eui';
import React from 'react';
-export const NoCreatorTip = (props: { iconType?: IconType }) => (
+const fallbackEntityNamePlural = i18n.translate(
+ 'contentManagement.userProfiles.fallbackEntityNamePlural',
+ { defaultMessage: 'objects' }
+);
+
+export const NoCreatorTip = (props: {
+ iconType?: IconType;
+ includeVersionTip?: boolean;
+ entityNamePlural?: string;
+}) => (
+ props.includeVersionTip ? (
+
+ ) : (
+
+ )
}
{...props}
/>
);
-export const NoUpdaterTip = (props: { iconType?: string }) => (
+export const NoUpdaterTip = (props: {
+ iconType?: string;
+ includeVersionTip?: boolean;
+ entityNamePlural?: string;
+}) => (
+ props.includeVersionTip ? (
+
+ ) : (
+
+ )
}
{...props}
/>
diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
index f6072169e5bda..7a8e5a40bb274 100644
--- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
+++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing.tsx
@@ -19,6 +19,7 @@ import { DASHBOARD_APP_ID, DASHBOARD_CONTENT_ID } from '../dashboard_constants';
import {
coreServices,
savedObjectsTaggingService,
+ serverlessService,
usageCollectionService,
} from '../services/kibana_services';
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
@@ -65,6 +66,7 @@ export const DashboardListing = ({
FormattedRelative,
favorites: dashboardFavoritesClient,
contentInsightsClient,
+ isKibanaVersioningEnabled: !serverlessService,
}}
>
{...tableListViewTableProps}>
diff --git a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing_table.tsx
index 96d9025f822ff..efcc0fe2cc644 100644
--- a/src/plugins/dashboard/public/dashboard_listing/dashboard_listing_table.tsx
+++ b/src/plugins/dashboard/public/dashboard_listing/dashboard_listing_table.tsx
@@ -16,7 +16,11 @@ import {
import { FormattedRelative, I18nProvider } from '@kbn/i18n-react';
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
-import { coreServices, savedObjectsTaggingService } from '../services/kibana_services';
+import {
+ coreServices,
+ savedObjectsTaggingService,
+ serverlessService,
+} from '../services/kibana_services';
import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
import { useDashboardListingTable } from './hooks/use_dashboard_listing_table';
import { DashboardListingProps, DashboardSavedObjectUserContent } from './types';
@@ -57,6 +61,7 @@ export const DashboardListingTable = ({
savedObjectsTagging={savedObjectsTaggingService?.getTaggingApi()}
FormattedRelative={FormattedRelative}
contentInsightsClient={contentInsightsClient}
+ isKibanaVersioningEnabled={!serverlessService}
>
<>
Date: Fri, 6 Dec 2024 10:58:20 -0500
Subject: [PATCH 07/27] [Data Usage] add error handling and tests for privilege
related errors (#203006)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- handling of 2 error cases to error handler
- `security_exception` due to lack of privileges. Metering api will
respond when one of the following isn't available as a user privilege
`monitor,view_index_metadata,manage,all`.
- `index_not_found_exception`. Metering api will respond with this when
no indices exist for the privileges it has access to or when no indices
are found.
- api integration tests for data_streams route for the following cases
- returns no data streams when there are none it has access to and
responds with appropriate message
- returns no data streams without necessary privileges and responds with
appropriate message
- returns data streams when user only has access to a subset of indices
with necessary privileges
- functional tests for same as above. these remain skipped due to not
being able to create data streams picked up by metering api since we
implemented filtering out zero storage size data streams, but useful for
local testing with some code changes.
### `security_exception` view
### `index_not_found_exception` view
---------
Co-authored-by: Ashokaditya
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../data_usage/server/common/errors.ts | 2 +-
x-pack/plugins/data_usage/server/errors.ts | 30 ++++
.../data_usage/server/routes/error_handler.ts | 18 +-
.../routes/internal/data_streams.test.ts | 35 +++-
.../routes/internal/data_streams_handler.ts | 7 +
.../routes/internal/usage_metrics.test.ts | 2 +-
.../data_usage/server/services/autoops_api.ts | 2 +-
.../data_usage/server/services/errors.ts | 10 --
.../data_usage/server/services/index.ts | 2 +-
.../test_suites/common/data_usage/index.ts | 1 +
.../tests/data_streams_privileges.ts | 155 ++++++++++++++++++
.../common/data_usage/privileges.ts | 101 +++++++++++-
12 files changed, 345 insertions(+), 20 deletions(-)
create mode 100644 x-pack/plugins/data_usage/server/errors.ts
delete mode 100644 x-pack/plugins/data_usage/server/services/errors.ts
create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams_privileges.ts
diff --git a/x-pack/plugins/data_usage/server/common/errors.ts b/x-pack/plugins/data_usage/server/common/errors.ts
index 7a43a10108be1..2b583570058e9 100644
--- a/x-pack/plugins/data_usage/server/common/errors.ts
+++ b/x-pack/plugins/data_usage/server/common/errors.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-export class BaseError extends Error {
+export class DataUsageError extends Error {
constructor(message: string, public readonly meta?: MetaType) {
super(message);
// For debugging - capture name of subclasses
diff --git a/x-pack/plugins/data_usage/server/errors.ts b/x-pack/plugins/data_usage/server/errors.ts
new file mode 100644
index 0000000000000..17363afe5da31
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/errors.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable max-classes-per-file */
+
+import { DataUsageError } from './common/errors';
+
+export class NotFoundError extends DataUsageError {}
+
+export class AutoOpsError extends DataUsageError {}
+
+export class NoPrivilegeMeteringError extends DataUsageError {
+ constructor() {
+ super(
+ 'You do not have the necessary privileges to access data stream statistics. Please contact your administrator.'
+ );
+ }
+}
+
+export class NoIndicesMeteringError extends DataUsageError {
+ constructor() {
+ super(
+ 'No data streams or indices are available for the current user. Ensure that the data streams or indices you are authorized to access have been created and contain data. If you believe this is an error, please contact your administrator.'
+ );
+ }
+}
diff --git a/x-pack/plugins/data_usage/server/routes/error_handler.ts b/x-pack/plugins/data_usage/server/routes/error_handler.ts
index b889d12674db5..055b09bb71b9e 100644
--- a/x-pack/plugins/data_usage/server/routes/error_handler.ts
+++ b/x-pack/plugins/data_usage/server/routes/error_handler.ts
@@ -7,10 +7,12 @@
import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server';
import { CustomHttpRequestError } from '../utils/custom_http_request_error';
-import { BaseError } from '../common/errors';
-import { AutoOpsError } from '../services/errors';
-
-export class NotFoundError extends BaseError {}
+import {
+ AutoOpsError,
+ NoPrivilegeMeteringError,
+ NoIndicesMeteringError,
+ NotFoundError,
+} from '../errors';
/**
* Default Data Usage Routes error handler
@@ -43,6 +45,14 @@ export const errorHandler = (
return res.notFound({ body: error });
}
+ if (error instanceof NoPrivilegeMeteringError) {
+ return res.forbidden({ body: error });
+ }
+
+ if (error instanceof NoIndicesMeteringError) {
+ return res.notFound({ body: error });
+ }
+
// Kibana CORE will take care of `500` errors when the handler `throw`'s, including logging the error
throw error;
};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts
index 374c4b9c82e7e..9efe61bd75118 100644
--- a/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts
+++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts
@@ -19,6 +19,7 @@ import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../../common';
import { createMockedDataUsageContext } from '../../mocks';
import { getMeteringStats } from '../../utils/get_metering_stats';
import { CustomHttpRequestError } from '../../utils';
+import { NoIndicesMeteringError, NoPrivilegeMeteringError } from '../../errors';
jest.mock('../../utils/get_metering_stats');
const mockGetMeteringStats = getMeteringStats as jest.Mock;
@@ -126,7 +127,7 @@ describe('registerDataStreamsRoute', () => {
});
});
- it('should return correct error if metering stats request fails', async () => {
+ it('should return correct error if metering stats request fails with an unknown error', async () => {
// using custom error for test here to avoid having to import the actual error class
mockGetMeteringStats.mockRejectedValue(
new CustomHttpRequestError('Error getting metring stats!')
@@ -144,6 +145,38 @@ describe('registerDataStreamsRoute', () => {
});
});
+ it('should return `not found` error if metering stats request fails when no indices', async () => {
+ mockGetMeteringStats.mockRejectedValue(
+ new Error(JSON.stringify({ message: 'index_not_found_exception' }))
+ );
+ const mockRequest = httpServerMock.createKibanaRequest({ body: {} });
+ const mockResponse = httpServerMock.createResponseFactory();
+ const mockRouter = mockCore.http.createRouter.mock.results[0].value;
+ const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls;
+ await handler(context, mockRequest, mockResponse);
+
+ expect(mockResponse.notFound).toHaveBeenCalledTimes(1);
+ expect(mockResponse.notFound).toHaveBeenCalledWith({
+ body: new NoIndicesMeteringError(),
+ });
+ });
+
+ it('should return `forbidden` error if metering stats request fails with privileges error', async () => {
+ mockGetMeteringStats.mockRejectedValue(
+ new Error(JSON.stringify({ message: 'security_exception' }))
+ );
+ const mockRequest = httpServerMock.createKibanaRequest({ body: {} });
+ const mockResponse = httpServerMock.createResponseFactory();
+ const mockRouter = mockCore.http.createRouter.mock.results[0].value;
+ const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls;
+ await handler(context, mockRequest, mockResponse);
+
+ expect(mockResponse.forbidden).toHaveBeenCalledTimes(1);
+ expect(mockResponse.forbidden).toHaveBeenCalledWith({
+ body: new NoPrivilegeMeteringError(),
+ });
+ });
+
it.each([
['no datastreams', {}, []],
['empty array', { datastreams: [] }, []],
diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
index 726aa157050f8..28967b9a0ee4a 100644
--- a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
+++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
@@ -10,6 +10,7 @@ import { DataUsageContext, DataUsageRequestHandlerContext } from '../../types';
import { errorHandler } from '../error_handler';
import { getMeteringStats } from '../../utils/get_metering_stats';
import { DataStreamsRequestQuery } from '../../../common/rest_types/data_streams';
+import { NoIndicesMeteringError, NoPrivilegeMeteringError } from '../../errors';
export const getDataStreamsHandler = (
dataUsageContext: DataUsageContext
@@ -45,6 +46,12 @@ export const getDataStreamsHandler = (
body,
});
} catch (error) {
+ if (error.message.includes('security_exception')) {
+ return errorHandler(logger, response, new NoPrivilegeMeteringError());
+ } else if (error.message.includes('index_not_found_exception')) {
+ return errorHandler(logger, response, new NoIndicesMeteringError());
+ }
+
return errorHandler(logger, response, error);
}
};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts
index f2bccd6d9c6b0..e6f98a97f0e93 100644
--- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts
+++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts
@@ -18,7 +18,7 @@ import type {
import { DATA_USAGE_METRICS_API_ROUTE } from '../../../common';
import { createMockedDataUsageContext } from '../../mocks';
import { CustomHttpRequestError } from '../../utils';
-import { AutoOpsError } from '../../services/errors';
+import { AutoOpsError } from '../../errors';
import { transformToUTCtime } from '../../../common/utils';
const timeRange = {
diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts
index 8f371d3004def..0fb9009bb95a5 100644
--- a/x-pack/plugins/data_usage/server/services/autoops_api.ts
+++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts
@@ -21,7 +21,7 @@ import {
type UsageMetricsRequestBody,
} from '../../common/rest_types';
import { AutoOpsConfig } from '../types';
-import { AutoOpsError } from './errors';
+import { AutoOpsError } from '../errors';
import { appContextService } from './app_context';
const AUTO_OPS_REQUEST_FAILED_PREFIX = '[AutoOps API] Request failed';
diff --git a/x-pack/plugins/data_usage/server/services/errors.ts b/x-pack/plugins/data_usage/server/services/errors.ts
deleted file mode 100644
index 0574e2a3c75fb..0000000000000
--- a/x-pack/plugins/data_usage/server/services/errors.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * 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 { BaseError } from '../common/errors';
-
-export class AutoOpsError extends BaseError {}
diff --git a/x-pack/plugins/data_usage/server/services/index.ts b/x-pack/plugins/data_usage/server/services/index.ts
index 56e449c8a5679..cf7a24e5ccba5 100644
--- a/x-pack/plugins/data_usage/server/services/index.ts
+++ b/x-pack/plugins/data_usage/server/services/index.ts
@@ -7,7 +7,7 @@
import { ValidationError } from '@kbn/config-schema';
import { Logger } from '@kbn/logging';
import type { MetricTypes } from '../../common/rest_types';
-import { AutoOpsError } from './errors';
+import { AutoOpsError } from '../errors';
import { AutoOpsAPIService } from './autoops_api';
export class DataUsageService {
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts
index cf31e1885d1d5..b0236e8dab722 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts
@@ -11,6 +11,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('Serverless Data Usage APIs', function () {
this.tags(['esGate']);
+ loadTestFile(require.resolve('./tests/data_streams_privileges'));
loadTestFile(require.resolve('./tests/data_streams'));
loadTestFile(require.resolve('./tests/metrics'));
});
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams_privileges.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams_privileges.ts
new file mode 100644
index 0000000000000..9ea213b6d94ea
--- /dev/null
+++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams_privileges.ts
@@ -0,0 +1,155 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { DataStreamsResponseBodySchemaBody } from '@kbn/data-usage-plugin/common/rest_types';
+import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '@kbn/data-usage-plugin/common';
+import type { RoleCredentials } from '@kbn/ftr-common-functional-services';
+import {
+ NoIndicesMeteringError,
+ NoPrivilegeMeteringError,
+} from '@kbn/data-usage-plugin/server/errors';
+import { FtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function ({ getService }: FtrProviderContext) {
+ const svlDatastreamsHelpers = getService('svlDatastreamsHelpers');
+ const svlCommonApi = getService('svlCommonApi');
+ const samlAuth = getService('samlAuth');
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
+ const testDataStreamName = 'test-data-stream';
+ const otherTestDataStreamName = 'other-test-data-stream';
+ let roleAuthc: RoleCredentials;
+
+ describe('privileges with custom roles', function () {
+ // custom role testing is not supported in MKI
+ // the metering api which this route calls requires one of: monitor,view_index_metadata,manage,all
+ this.tags(['skipSvlOblt', 'skipMKI']);
+ before(async () => {
+ await svlDatastreamsHelpers.createDataStream(testDataStreamName);
+ await svlDatastreamsHelpers.createDataStream(otherTestDataStreamName);
+ });
+ after(async () => {
+ await svlDatastreamsHelpers.deleteDataStream(testDataStreamName);
+ await svlDatastreamsHelpers.deleteDataStream(otherTestDataStreamName);
+ });
+ afterEach(async () => {
+ await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
+ await samlAuth.deleteCustomRole();
+ });
+ it('returns all data streams for indices with necessary privileges', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ indices: [{ names: ['*'], privileges: ['all'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole');
+ const res = await supertestWithoutAuth
+ .get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
+ .query({ includeZeroStorage: true })
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .set('elastic-api-version', '1');
+
+ const dataStreams: DataStreamsResponseBodySchemaBody = res.body;
+ const foundTestDataStream = dataStreams.find((stream) => stream.name === testDataStreamName);
+ const foundTestDataStream2 = dataStreams.find(
+ (stream) => stream.name === otherTestDataStreamName
+ );
+ expect(res.statusCode).to.be(200);
+ expect(foundTestDataStream?.name).to.be(testDataStreamName);
+ expect(foundTestDataStream2?.name).to.be(otherTestDataStreamName);
+ });
+ it('returns data streams for only a subset of indices with necessary privileges', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ indices: [{ names: ['test-data-stream*'], privileges: ['all'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole');
+ const res = await supertestWithoutAuth
+ .get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
+ .query({ includeZeroStorage: true })
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .set('elastic-api-version', '1');
+
+ const dataStreams: DataStreamsResponseBodySchemaBody = res.body;
+ const foundTestDataStream = dataStreams.find((stream) => stream.name === testDataStreamName);
+ const dataStreamNoPermission = dataStreams.find(
+ (stream) => stream.name === otherTestDataStreamName
+ );
+
+ expect(res.statusCode).to.be(200);
+ expect(foundTestDataStream?.name).to.be(testDataStreamName);
+ expect(dataStreamNoPermission?.name).to.be(undefined);
+ });
+
+ it('returns no data streams without necessary privileges', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ indices: [{ names: ['*'], privileges: ['write'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole');
+ const res = await supertestWithoutAuth
+ .get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
+ .query({ includeZeroStorage: true })
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .set('elastic-api-version', '1');
+
+ expect(res.statusCode).to.be(403);
+ expect(res.body.message).to.contain(new NoPrivilegeMeteringError().message);
+ });
+
+ it('returns no data streams when there are none it has access to', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ indices: [{ names: ['none*'], privileges: ['all'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('customRole');
+ const res = await supertestWithoutAuth
+ .get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
+ .query({ includeZeroStorage: true })
+ .set(svlCommonApi.getInternalRequestHeader())
+ .set(roleAuthc.apiKeyHeader)
+ .set('elastic-api-version', '1');
+
+ expect(res.statusCode).to.be(404);
+ expect(res.body.message).to.contain(new NoIndicesMeteringError().message);
+ });
+ });
+}
diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/privileges.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/privileges.ts
index e926b43aedfc4..4efcdd2586a85 100644
--- a/x-pack/test_serverless/functional/test_suites/common/data_usage/privileges.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/privileges.ts
@@ -5,6 +5,11 @@
* 2.0.
*/
+import expect from '@kbn/expect';
+import {
+ NoIndicesMeteringError,
+ NoPrivilegeMeteringError,
+} from '@kbn/data-usage-plugin/server/errors';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
@@ -12,7 +17,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const samlAuth = getService('samlAuth');
const retry = getService('retry');
+ const es = getService('es');
const dataUsageAppUrl = 'management/data/data_usage';
+ const toasts = getService('toasts');
const navigateAndVerify = async (expectedVisible: boolean) => {
await pageObjects.common.navigateToApp('management');
@@ -32,6 +39,32 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
};
describe('privileges', function () {
+ before(async () => {
+ await es.indices.putIndexTemplate({
+ name: 'test-datastream',
+ body: {
+ index_patterns: ['test-datastream'],
+ data_stream: {},
+ priority: 200,
+ },
+ });
+
+ await es.indices.createDataStream({ name: 'test-datastream' });
+ await es.indices.putIndexTemplate({
+ name: 'no-permission-test-datastream',
+ body: {
+ index_patterns: ['no-permission-test-datastream'],
+ data_stream: {},
+ priority: 200,
+ },
+ });
+
+ await es.indices.createDataStream({ name: 'no-permission-test-datastream' });
+ });
+ after(async () => {
+ await es.indices.deleteDataStream({ name: 'test-datastream' });
+ await es.indices.deleteDataStream({ name: 'no-permission-test-datastream' });
+ });
it('renders for the admin role', async () => {
await pageObjects.svlCommonPage.loginAsAdmin();
await navigateAndVerify(true);
@@ -63,7 +96,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
afterEach(async () => {
await samlAuth.deleteCustomRole();
});
- it('renders with a custom role that has the monitor cluster privilege', async () => {
+ it('renders with a custom role that has the privileges cluster: monitor and indices all', async () => {
await samlAuth.setCustomRole({
elasticsearch: {
cluster: ['monitor'],
@@ -97,6 +130,72 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.svlCommonPage.loginWithCustomRole();
await navigateAndVerify(false);
});
+
+ describe.skip('with custom role and data streams', function () {
+ // skip in all environments. requires a code change to the data_streams route
+ // to allow zero storage data streams to not be filtered out, but useful for testing.
+ // the api integration tests can pass a flag to get around this case but we can't in the UI.
+ // metering api requires one of: monitor,view_index_metadata,manage,all
+ it('does not load data streams without necessary index privilege for any index', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ cluster: ['monitor'],
+ indices: [{ names: ['*'], privileges: ['read'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ await pageObjects.svlCommonPage.loginWithCustomRole();
+ await navigateAndVerify(true);
+ const toastContent = await toasts.getContentByIndex(1);
+ expect(toastContent).to.contain(NoPrivilegeMeteringError);
+ });
+
+ it('does load data streams with necessary index privilege for some indices', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ cluster: ['monitor'],
+ indices: [
+ { names: ['test-datastream*'], privileges: ['all'] },
+ { names: ['.*'], privileges: ['read'] },
+ ],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ await pageObjects.svlCommonPage.loginWithCustomRole();
+ await navigateAndVerify(true);
+ });
+ it('handles error when no data streams that it has permission to exist (index_not_found_exception)', async () => {
+ await samlAuth.setCustomRole({
+ elasticsearch: {
+ cluster: ['monitor'],
+ indices: [{ names: ['none*'], privileges: ['all'] }],
+ },
+ kibana: [
+ {
+ base: ['all'],
+ feature: {},
+ spaces: ['*'],
+ },
+ ],
+ });
+ await pageObjects.svlCommonPage.loginWithCustomRole();
+ await navigateAndVerify(true);
+ const toastContent = await toasts.getContentByIndex(1);
+ expect(toastContent).to.contain(NoIndicesMeteringError);
+ });
+ });
});
});
};
From e255ca2728decbfc4879d1c70f75e99a6134575f Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Fri, 6 Dec 2024 10:58:55 -0500
Subject: [PATCH 08/27] Dependency ownership for Kibana Operations team, part 1
(#202904)
## Summary
This updates our `renovate.json` configuration to mark the Kibana
Operations team as owners of their set of dependencies.
---------
Co-authored-by: Jeramy Soucy
---
renovate.json | 1133 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 1094 insertions(+), 39 deletions(-)
diff --git a/renovate.json b/renovate.json
index 1121043865aa2..71f80e1b67e11 100644
--- a/renovate.json
+++ b/renovate.json
@@ -712,7 +712,9 @@
{
"groupName": "babel",
"matchDepNames": [
- "@types/babel__core"
+ "@emotion/babel-preset-css-prop",
+ "@types/babel__core",
+ "babel-loader"
],
"matchDepPatterns": [
"^@babel",
@@ -726,16 +728,829 @@
],
"labels": [
"Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "backport",
+ "matchDepNames": [
+ "backport"
+ ],
+ "matchDepPatterns": [],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "bazel",
+ "matchDepNames": [],
+ "matchDepPatterns": [
+ "^@bazel"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": false
+ },
+ {
+ "groupName": "typescript",
+ "matchDepNames": [
+ "typescript"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "json-stable-stringify",
+ "matchDepNames": [
+ "json-stable-stringify",
+ "@types/json-stable-stringify"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "jsdom",
+ "matchDepNames": [
+ "jsdom",
+ "@types/jsdom"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "license-checker",
+ "matchDepNames": [
+ "license-checker",
+ "@types/license-checker"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "loader-utils",
+ "matchDepNames": [
+ "loader-utils",
+ "@types/loader-utils"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "micromatch/minimatch",
+ "matchDepNames": [
+ "micromatch",
+ "minimatch",
+ "@types/micromatch",
+ "@types/minimatch"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "nock",
+ "matchDepNames": [
+ "nock",
+ "@types/nock"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "prettier",
+ "matchDepNames": [
+ "prettier",
+ "eslint-plugin-prettier",
+ "eslint-config-prettier"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "allowedVersions": "<3.0",
+ "enabled": true
+ },
+ {
+ "groupName": "eslint",
+ "matchDepNames": [
+ "^@typescript-eslint",
+ "@elastic/eslint-plugin-eui",
+ "eslint",
+ "eslint-plugin-ban",
+ "eslint-plugin-cypress",
+ "eslint-plugin-depend",
+ "eslint-plugin-eslint-comments",
+ "eslint-plugin-formatjs",
+ "eslint-plugin-import",
+ "eslint-plugin-jest",
+ "eslint-plugin-jsx-a11y",
+ "eslint-plugin-mocha",
+ "eslint-plugin-no-unsanitized",
+ "eslint-plugin-node",
+ "eslint-plugin-react",
+ "eslint-plugin-react-hooks",
+ "eslint-plugin-react-perf",
+ "eslint-traverse",
+ "@types/eslint"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "execa",
+ "matchDepNames": [
+ "execa"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "@elastic/makelogs",
+ "matchDepNames": [
+ "@elastic/makelogs"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "polyfills",
+ "matchDepNames": [
+ "blob-polyfill",
+ "core-js"
+ ],
+ "matchDepPatterns": [
+ "polyfill"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "archiver",
+ "matchDepNames": [
+ "archiver",
+ "@types/archiver"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "CLI tooling",
+ "matchDepNames": [
+ "argsplit",
+ "chalk",
+ "cli-progress",
+ "cli-table3",
+ "commander",
+ "dedent",
+ "getopts",
+ "listr2",
+ "ora",
+ "@parcel/watcher",
+ "@types/cli-progress",
+ "@types/dedent",
+ "@types/ora"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "ejs",
+ "matchDepNames": [
+ "ejs",
+ "@types/ejs"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "normalize-path",
+ "matchDepNames": [
+ "normalize-path",
+ "@types/normalize-path"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "@statoscope/webpack-plugin",
+ "matchDepNames": [
+ "@statoscope/webpack-plugin"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "mocha",
+ "matchDepNames": [
+ "mocha",
+ "mocha-junit-reporter",
+ "mocha-multi-reporters",
+ "mochawesome",
+ "mochawesome-merge",
+ "regenerate"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "@octokit/rest",
+ "matchDepNames": [
+ "@octokit/rest"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "picomatch",
+ "matchDepNames": [
+ "picomatch",
+ "@types/picomatch"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "postcss",
+ "matchDepNames": [
+ "postcss",
+ "postcss-scss"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "resolve",
+ "matchDepNames": [
+ "resolve",
+ "@types/resolve"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "source-map",
+ "matchDepNames": [
+ "source-map",
+ "source-map-support",
+ "@types/source-map-support"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "stylelint",
+ "matchDepNames": [
+ "stylelint",
+ "stylelint-scss"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "vega related modules",
+ "matchDepNames": [
+ "vega",
+ "vega-lite",
+ "vega-schema-url-parser",
+ "vega-tooltip"
+ ],
+ "reviewers": [
+ "team:kibana-visualizations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Feature:Vega",
+ "Team:Visualizations"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "vinyl",
+ "matchDepNames": [
+ "vinyl",
+ "vinyl-fs",
+ "@types/vinyl",
+ "@types/vinyl-fs"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "watchpack",
+ "matchDepNames": [
+ "watchpack",
+ "@types/watchpack"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "webpack",
+ "matchDepNames": [
+ "autoprefixer",
+ "clean-webpack-plugin",
+ "css-loader",
+ "file-loader",
+ "mini-css-extract-plugin",
+ "postcss-loader",
+ "raw-loader",
+ "sass-loader",
+ "style-loader",
+ "url-loader",
+ "val-loader",
+ "webpack",
+ "webpack-bundle-analyzer",
+ "webpack-cli",
+ "webpack-dev-server",
+ "webpack-merge",
+ "webpack-sources",
+ "webpack-visualizer-plugin2",
+ "@types/webpack",
+ "@types/webpack-bundle-analyzer",
+ "@types/webpack-env",
+ "@types/webpack-merge",
+ "@types/webpack-sources"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "yargs",
+ "matchDepNames": [
+ "yargs",
+ "@types/yargs"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "yauzl/yazl",
+ "matchDepNames": [
+ "yazl",
+ "yauzl",
+ "@types/yazl",
+ "@types/yauzl"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "package.json/yarn.lock utils",
+ "matchDepNames": [
+ "@yarnpkg/lockfile",
+ "sort-package-json"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "aggregate-error",
+ "matchDepNames": [
+ "aggregate-error"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "borc",
+ "matchDepNames": [
+ "borc"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "cpy",
+ "matchDepNames": [
+ "cpy"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "cssnano",
+ "matchDepNames": [
+ "cssnano",
+ "cssnano-preset-default"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "del",
+ "matchDepNames": [
+ "del"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
},
{
- "groupName": "typescript",
+ "groupName": "exit-hook",
"matchDepNames": [
- "typescript",
- "@types/jsdom"
+ "exit-hook"
],
"reviewers": [
"team:kibana-operations"
@@ -745,17 +1560,16 @@
],
"labels": [
"Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
},
{
- "groupName": "prettier",
+ "groupName": "expect",
"matchDepNames": [
- "prettier",
- "eslint-plugin-prettier",
- "eslint-config-prettier"
+ "expect"
],
"reviewers": [
"team:kibana-operations"
@@ -765,16 +1579,16 @@
],
"labels": [
"Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
- "allowedVersions": "<3.0",
"enabled": true
},
{
- "groupName": "typescript-eslint",
- "matchDepPatterns": [
- "^@typescript-eslint"
+ "groupName": "expiry-js",
+ "matchDepNames": [
+ "expiry-js"
],
"reviewers": [
"team:kibana-operations"
@@ -784,15 +1598,16 @@
],
"labels": [
"Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
},
{
- "groupName": "eslint-plugin-depend",
- "matchDepPatterns": [
- "eslint-plugin-depend"
+ "groupName": "globby",
+ "matchDepNames": [
+ "globby"
],
"reviewers": [
"team:kibana-operations"
@@ -802,18 +1617,35 @@
],
"labels": [
"Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
},
{
- "groupName": "polyfills",
+ "groupName": "ignore",
"matchDepNames": [
- "core-js"
+ "ignore"
],
- "matchDepPatterns": [
- "polyfill"
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "json-schema-typed",
+ "matchDepNames": [
+ "json-schema-typed"
],
"reviewers": [
"team:kibana-operations"
@@ -823,15 +1655,16 @@
],
"labels": [
"Team:Operations",
+ "backport:all-open",
"release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
},
{
- "groupName": "CLI tooling",
+ "groupName": "lmdb",
"matchDepNames": [
- "listr2"
+ "lmdb"
],
"reviewers": [
"team:kibana-operations"
@@ -848,22 +1681,211 @@
"enabled": true
},
{
- "groupName": "vega related modules",
+ "groupName": "pirates",
"matchDepNames": [
- "vega",
- "vega-lite",
- "vega-schema-url-parser",
- "vega-tooltip"
+ "pirates"
],
"reviewers": [
- "team:kibana-visualizations"
+ "team:kibana-operations"
],
"matchBaseBranches": [
"main"
],
"labels": [
- "Feature:Vega",
- "Team:Visualizations"
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "piscina",
+ "matchDepNames": [
+ "piscina"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "proxy-from-env",
+ "matchDepNames": [
+ "proxy-from-env"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "svgo",
+ "matchDepNames": [
+ "svgo"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "symbol-observable",
+ "matchDepNames": [
+ "symbol-observable"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "tree-kill",
+ "matchDepNames": [
+ "tree-kill"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "ts-morph",
+ "matchDepNames": [
+ "ts-morph"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "tsd",
+ "matchDepNames": [
+ "tsd"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "tslib",
+ "matchDepNames": [
+ "tslib"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "yarn-deduplicate",
+ "matchDepNames": [
+ "yarn-deduplicate"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
+ {
+ "groupName": "http2",
+ "matchDepNames": [
+ "http2-proxy",
+ "http2-wrapper"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "backport:all-open",
+ "release_note:skip"
],
"minimumReleaseAge": "7 days",
"enabled": true
@@ -971,7 +1993,8 @@
],
"labels": [
"Team:Operations",
- "release_note:skip"
+ "release_note:skip",
+ "backport:all-open"
],
"minimumReleaseAge": "7 days",
"enabled": true
@@ -995,11 +2018,35 @@
"minimumReleaseAge": "7 days",
"enabled": true
},
+ {
+ "groupName": "inquirer",
+ "matchDepNames": [
+ "inquirer",
+ "@types/inquirer"
+ ],
+ "reviewers": [
+ "team:kibana-operations"
+ ],
+ "matchBaseBranches": [
+ "main"
+ ],
+ "labels": [
+ "Team:Operations",
+ "release_note:skip",
+ "backport:all-open"
+ ],
+ "minimumReleaseAge": "7 days",
+ "enabled": true
+ },
{
"groupName": "minify",
"matchDepNames": [
+ "gulp-brotli",
+ "gulp-postcss",
"gulp-terser",
- "terser"
+ "terser",
+ "terser-webpack-plugin",
+ "@types/gulp"
],
"reviewers": [
"team:kibana-operations"
@@ -1009,7 +2056,8 @@
],
"labels": [
"Team:Operations",
- "release_note:skip"
+ "release_note:skip",
+ "backport:all-open"
],
"minimumReleaseAge": "7 days",
"enabled": true
@@ -1021,7 +2069,8 @@
"@testing-library/jest-dom",
"@testing-library/react",
"@testing-library/react-hooks",
- "@testing-library/user-event"
+ "@testing-library/user-event",
+ "@types/testing-library__jest-dom"
],
"reviewers": [
"team:kibana-operations"
@@ -1031,7 +2080,8 @@
],
"labels": [
"Team:Operations",
- "release_note:skip"
+ "release_note:skip",
+ "backport:all-open"
],
"minimumReleaseAge": "7 days",
"enabled": true
@@ -1039,12 +2089,16 @@
{
"groupName": "jest",
"matchDepNames": [
+ "@emotion/jest",
"@jest/console",
"@jest/reporters",
+ "@jest/transform",
"@jest/types",
+ "@types/jest",
"babel-jest",
"expect",
"jest",
+ "jest-canvas-mock",
"jest-cli",
"jest-config",
"jest-diff",
@@ -1052,7 +2106,10 @@
"jest-matcher-utils",
"jest-mock",
"jest-runtime",
- "jest-snapshot"
+ "jest-specific-snapshot",
+ "jest-snapshot",
+ "jest-styled-components",
+ "mutation-observer"
],
"reviewers": [
"team:kibana-operations"
@@ -1062,7 +2119,8 @@
],
"labels": [
"Team:Operations",
- "release_note:skip"
+ "release_note:skip",
+ "backport:all-open"
],
"minimumReleaseAge": "7 days",
"enabled": true
@@ -1078,9 +2136,6 @@
"matchDepPatterns": [
"^@storybook"
],
- "excludeDepNames": [
- "@storybook/testing-react"
- ],
"labels": [
"Team:Operations",
"release_note:skip",
From e3bc923b9a414b31c944ce856e91d2664631edf2 Mon Sep 17 00:00:00 2001
From: Pierre Gayvallet
Date: Fri, 6 Dec 2024 17:55:59 +0100
Subject: [PATCH 09/27] Add documentation for airgap install of the product
documentation (#202912)
## Summary
Part of https://github.com/elastic/kibana/issues/199999
Add documentation regarding how to install the AI Assistant product
documentation on air-gap environments.
---
docs/settings/ai-assistant-settings.asciidoc | 117 ++++++++++++++++++
docs/setup/settings.asciidoc | 1 +
.../resources/base/bin/kibana-docker | 1 +
3 files changed, 119 insertions(+)
create mode 100644 docs/settings/ai-assistant-settings.asciidoc
diff --git a/docs/settings/ai-assistant-settings.asciidoc b/docs/settings/ai-assistant-settings.asciidoc
new file mode 100644
index 0000000000000..a2b62c75a9247
--- /dev/null
+++ b/docs/settings/ai-assistant-settings.asciidoc
@@ -0,0 +1,117 @@
+[role="xpack"]
+[[ai-assistant-settings-kb]]
+=== AI Assistant settings in {kib}
+++++
+AI Assistant settings
+++++
+
+`xpack.productDocBase.artifactRepositoryUrl`::
+Url of the repository to use to download and install the Elastic product documentation artifacts for the AI assistants.
+Defaults to `https://kibana-knowledge-base-artifacts.elastic.co`
+
+[[configuring-product-doc-for-airgap]]
+==== Configuring product documentation for air-gapped environments
+
+Installing product documentation requires network access to its artifact repository.
+For air-gapped environments, or environments where remote network traffic is blocked or filtered,
+the artifact repository must be manually deployed somewhere accessible by the Kibana deployment.
+
+Deploying a custom product documentation repository can be done in 2 ways: using a S3 bucket, or using a CDN.
+
+===== Deploying using a S3 bucket
+
+*1. Download the artifacts for your current {kib} version*
+
+The artifact names follow this pattern: `kb-product-doc-{productName}-{versionMajor}.{versionMinor}.zip`
+
+The available products are:
+- elasticsearch
+- kibana
+- observability
+- security
+
+You must download, from the source repository (`https://kibana-knowledge-base-artifacts.elastic.co/`),
+the artifacts for your current version of Kibana.
+
+For example, for Kibana 8.16:
+- `kb-product-doc-elasticsearch-8.16.zip`
+- `kb-product-doc-kibana-8.16.zip`
+- `kb-product-doc-observability-8.16.zip`
+- `kb-product-doc-security-8.16.zip`
+
+*2. Upload the artifacts to your local S3 bucket*
+
+Upload the artifact files to your custom S3 bucket, then make sure that they are properly listed in the bucket's index, similar to
+the bucket listing displayed when accessing `https://kibana-knowledge-base-artifacts.elastic.co/` in a browser.
+
+*3. Configure {kib} to use the custom repository*
+
+Add the following line to your {kib} configuration file:
+
+[source,yaml]
+----
+# Replace with the root of your custom bucket
+xpack.productDocBase.artifactRepositoryUrl: "https://my-custom-repository.example.com"
+----
+
+*4. Restart {kib}*
+
+You should then be able to install the product documentation feature from the AI assistant management page.
+
+===== Deploying using a CDN
+
+Deploying using a CDN is quite similar to the S3 bucket approach. The main difference will be that we will need to manually
+generate the bucket listing and set it as the CDN folder's index page.
+
+*1. Download the artifacts for your current {kib} version*
+
+Following the step from the `Deploying using a S3 bucket` section
+
+*2. Upload the artifacts to the CDN*
+
+Create a folder in your CDN, and upload the artifacts to it.
+
+*3. Create and upload the bucket listing*
+
+Generate the S3 bucket listing xml file for the folder.
+
+To do that, copy the following template, and replace the versions in the `` tags with your current version of {kib}.
+
+For example for {kib} 8.17, replace all `8.16` occurrences in the file with `8.17`.
+
+[source,xml]
+----
+
+ kibana-ai-assistant-kb-artifacts
+ false
+
+ kb-product-doc-elasticsearch-8.16.zip
+
+
+ kb-product-doc-kibana-8.16.zip
+
+
+ kb-product-doc-observability-8.16.zip
+
+
+ kb-product-doc-security-8.16.zip
+
+
+----
+
+Then upload that xml file to the same CDN folder where the artifacts were uploaded, and then configure the folder to have that file
+served as the folder's index.
+
+*4. Configure {kib} to use the custom repository*
+
+Add the following line to your {kib} configuration file:
+
+[source,yaml]
+----
+# Replace with the path to the CDN folder previously configured
+xpack.productDocBase.artifactRepositoryUrl: "https://my-custom-repository.example.com"
+----
+
+*5. Restart {kib}*
+
+You should then be able to install the product documentation feature from the AI assistant management page.
\ No newline at end of file
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index 4e452c63cf3b1..51064981fab85 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -650,6 +650,7 @@ Set this value to false to disable the Upgrade Assistant UI. *Default: true*
Set this value to change the {kib} interface language.
Valid locales are: `en`, `zh-CN`, `ja-JP`, `fr-FR`. *Default: `en`*
+include::{kibana-root}/docs/settings/ai-assistant-settings.asciidoc[]
include::{kibana-root}/docs/settings/alert-action-settings.asciidoc[leveloffset=+1]
include::{kibana-root}/docs/settings/apm-settings.asciidoc[]
include::{kibana-root}/docs/settings/banners-settings.asciidoc[]
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
index e751997a1fb78..4bd937d3fc8fd 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
@@ -313,6 +313,7 @@ kibana_vars=(
xpack.observability.unsafe.alertDetails.uptime.enabled
xpack.observability.unsafe.alertDetails.observability.enabled
xpack.observability.unsafe.thresholdRule.enabled
+ xpack.productDocBase.artifactRepositoryUrl
xpack.reporting.capture.browser.autoDownload
xpack.reporting.capture.browser.chromium.disableSandbox
xpack.reporting.capture.browser.chromium.maxScreenshotDimension
From 2f95dee8f4b534a10e9bf2ee5d70a5c5730b70d7 Mon Sep 17 00:00:00 2001
From: Julia Rechkunova
Date: Fri, 6 Dec 2024 17:58:47 +0100
Subject: [PATCH 10/27] [Discover] Remove discover:searchFieldsFromSource
setting (#202679)
- Closes https://github.com/elastic/kibana/issues/196501
## Summary
The PR removes `discover:searchFieldsFromSource` Advanced Setting and
the associated code.
This breaking change is planned for v9.
The setting was marked as deprecated in v8.15
https://github.com/elastic/kibana/pull/185871
### Checklist
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
docs/management/advanced-options.asciidoc | 6 -
.../data_table_react_embeddable.tsx | 1 -
packages/kbn-discover-utils/index.ts | 1 -
packages/kbn-discover-utils/src/constants.ts | 1 -
.../settings/setting_ids/index.ts | 1 -
packages/kbn-unified-data-table/README.md | 3 -
.../src/components/actions/columns.test.ts | 1 -
.../src/components/actions/columns.ts | 22 +-
.../src/components/data_table.test.tsx | 2 -
.../src/components/data_table.tsx | 7 -
.../src/hooks/use_data_grid_columns.test.tsx | 4 +-
.../src/hooks/use_data_grid_columns.ts | 25 +-
.../src/utils/get_render_cell_value.test.tsx | 17 -
.../src/utils/get_render_cell_value.tsx | 7 +-
.../field_list_sidebar.tsx | 8 +-
.../with_discover_services.tsx | 3 -
.../discover/public/__mocks__/ui_settings.ts | 3 -
.../application/context/context_app.tsx | 7 +-
.../context/context_app_content.test.tsx | 1 -
.../context/context_app_content.tsx | 3 -
.../hooks/use_context_app_fetch.test.tsx | 1 -
.../context/hooks/use_context_app_fetch.tsx | 20 +-
.../context.predecessors.test.ts.snap | 9 +
.../context.successors.test.ts.snap | 9 +
.../__snapshots__/context.test.ts.snap | 23 +-
.../context/services/anchor.test.ts | 71 +-
.../application/context/services/anchor.ts | 12 +-
.../services/context.predecessors.test.ts | 4 +-
.../services/context.successors.test.ts | 5 +-
.../context/services/context.test.ts | 9 +-
.../application/context/services/context.ts | 14 +-
.../context/services/context_state.test.ts | 12 +-
.../application/doc/components/doc.test.tsx | 8 -
.../field_stats_table/field_stats_table.tsx | 2 +-
.../components/layout/discover_documents.tsx | 6 +-
.../components/layout/discover_layout.tsx | 9 +-
.../main/data_fetching/fetch_all.test.ts | 3 -
.../main/data_fetching/fetch_all.ts | 1 -
.../update_search_source.test.ts | 50 +-
.../data_fetching/update_search_source.ts | 11 +-
.../discover_data_state_container.ts | 7 +-
.../utils/get_state_defaults.ts | 9 +-
.../search_embeddable_grid_component.tsx | 9 +-
.../public/embeddable/initialize_fetch.ts | 3 -
.../utils/update_search_source.test.ts | 24 -
.../embeddable/utils/update_search_source.ts | 10 +-
.../public/utils/get_sharing_data.test.ts | 48 +-
.../discover/public/utils/get_sharing_data.ts | 36 +-
.../discover/public/utils/state_helpers.ts | 37 +-
.../server/locator/columns_from_locator.ts | 9 +-
src/plugins/discover/server/ui_settings.ts | 24 -
.../esql_datagrid/public/data_grid.tsx | 1 -
.../server/collectors/management/schema.ts | 4 -
.../server/collectors/management/types.ts | 1 -
src/plugins/telemetry/schema/oss_plugins.json | 6 -
.../doc_viewer_source/source.test.tsx | 11 -
.../components/doc_viewer_source/source.tsx | 5 -
.../public/hooks/use_es_doc_search.test.tsx | 100 +--
.../public/hooks/use_es_doc_search.ts | 50 +-
.../discover/group4/_discover_fields_api.ts | 26 +-
.../apps/discover/group5/_field_data.ts | 105 ---
.../group5/_field_data_with_fields_api.ts | 27 +-
.../apps/discover/group5/_source_filters.ts | 1 -
test/functional/apps/discover/group5/index.ts | 1 -
.../discover/group6/_field_stats_table.ts | 51 +-
.../apps/discover/group6/_sidebar.ts | 41 -
.../_indexpattern_with_unmapped_fields.ts | 2 -
.../discover/group7/_runtime_fields_editor.ts | 1 -
.../cloud_security_data_table.tsx | 4 -
.../data_view_management.tsx | 8 +-
.../index_data_visualizer_view.tsx | 5 +-
.../components/common/documents_table.tsx | 1 -
.../unified_components/data_table/index.tsx | 1 -
.../timeline/unified_components/index.tsx | 1 -
.../translations/translations/fr-FR.json | 3 -
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
.../discover/__snapshots__/reporting.snap | 745 ------------------
.../functional/apps/discover/reporting.ts | 20 -
.../common/discover/group6/_sidebar.ts | 43 -
.../x_pack/__snapshots__/reporting.snap | 745 ------------------
.../common/discover/x_pack/reporting.ts | 23 -
82 files changed, 182 insertions(+), 2474 deletions(-)
delete mode 100644 test/functional/apps/discover/group5/_field_data.ts
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index a2af3b91931af..ef6d6306792b1 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -309,12 +309,6 @@ Limits the number of rows per page in the document table.
[[discover-sample-size]]`discover:sampleSize`::
Sets the maximum number of rows for the entire document table. This is the maximum number of documents fetched from {es}.
-[[discover-searchFieldsFromSource]]`discover:searchFieldsFromSource`::
-deprecated:[8.15.0]
-Load fields from the original JSON {ref}/mapping-source-field.html[`_source`].
-When disabled, *Discover* loads fields using the {es} search API's
-{ref}/search-fields.html#search-fields-param[`fields`] parameter.
-
[[discover-searchonpageload]]`discover:searchOnPageLoad`::
Controls whether a search is executed when *Discover* first loads. This setting
does not have an effect when loading a saved search.
diff --git a/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx
index 40bb9186e16a5..54046eb5afa02 100644
--- a/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx
+++ b/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx
@@ -112,7 +112,6 @@ export const getDataTableFactory = (
dataView={dataView}
sampleSizeState={100}
columns={fields ?? []}
- useNewFieldsApi={true}
services={allServices}
onSetColumns={() => {}}
ariaLabelledBy="dataTableReactEmbeddableAria"
diff --git a/packages/kbn-discover-utils/index.ts b/packages/kbn-discover-utils/index.ts
index 93481c681d930..d344e2640371f 100644
--- a/packages/kbn-discover-utils/index.ts
+++ b/packages/kbn-discover-utils/index.ts
@@ -22,7 +22,6 @@ export {
SAMPLE_ROWS_PER_PAGE_SETTING,
SAMPLE_SIZE_SETTING,
SEARCH_EMBEDDABLE_TYPE,
- SEARCH_FIELDS_FROM_SOURCE,
SEARCH_ON_PAGE_LOAD_SETTING,
SHOW_FIELD_STATISTICS,
SHOW_MULTIFIELDS,
diff --git a/packages/kbn-discover-utils/src/constants.ts b/packages/kbn-discover-utils/src/constants.ts
index 005f6f7687109..068741ef84ad6 100644
--- a/packages/kbn-discover-utils/src/constants.ts
+++ b/packages/kbn-discover-utils/src/constants.ts
@@ -20,7 +20,6 @@ export const ROW_HEIGHT_OPTION = 'discover:rowHeightOption';
export const SAMPLE_ROWS_PER_PAGE_SETTING = 'discover:sampleRowsPerPage';
export const SAMPLE_SIZE_SETTING = 'discover:sampleSize';
export const SEARCH_EMBEDDABLE_TYPE = 'search';
-export const SEARCH_FIELDS_FROM_SOURCE = 'discover:searchFieldsFromSource';
export const SEARCH_ON_PAGE_LOAD_SETTING = 'discover:searchOnPageLoad';
export const SHOW_FIELD_STATISTICS = 'discover:showFieldStatistics';
export const SHOW_MULTIFIELDS = 'discover:showMultiFields';
diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts
index 438e216ab8f32..0a1b3e2bcdade 100644
--- a/packages/kbn-management/settings/setting_ids/index.ts
+++ b/packages/kbn-management/settings/setting_ids/index.ts
@@ -80,7 +80,6 @@ export const DISCOVER_MODIFY_COLUMNS_ON_SWITCH_ID = 'discover:modifyColumnsOnSwi
export const DISCOVER_ROW_HEIGHT_OPTION_ID = 'discover:rowHeightOption';
export const DISCOVER_SAMPLE_ROWS_PER_PAGE_ID = 'discover:sampleRowsPerPage';
export const DISCOVER_SAMPLE_SIZE_ID = 'discover:sampleSize';
-export const DISCOVER_SEARCH_FIELDS_FROM_SOURCE_ID = 'discover:searchFieldsFromSource';
export const DISCOVER_SEARCH_ON_PAGE_LOAD_ID = 'discover:searchOnPageLoad';
export const DISCOVER_SHOW_FIELD_STATISTICS_ID = 'discover:showFieldStatistics';
export const DISCOVER_SHOW_MULTI_FIELDS_ID = 'discover:showMultiFields';
diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md
index 0dd94c7c0977d..a6927eaae69b4 100644
--- a/packages/kbn-unified-data-table/README.md
+++ b/packages/kbn-unified-data-table/README.md
@@ -26,7 +26,6 @@ Props description:
| **showFullScreenButton** | (optional)boolean | Determines whether the full screen button should be displayed. |
| **isSortEnabled** | (optional)boolean | Manage user sorting control. |
| **sort** | SortOrder[] | Current sort setting. |
-| **useNewFieldsApi** | boolean | How the data is fetched. |
| **isPaginationEnabled** | (optional)boolean | Manage pagination control. |
| **controlColumnIds** | (optional)string[] | List of used control columns (available: 'openDetails', 'select'). |
| **rowHeightState** | (optional)number | Row height from state. |
@@ -145,7 +144,6 @@ Usage example:
renderCustomGridBody={renderCustomGridBody}
rowsPerPageOptions={[10, 30, 40, 100]}
showFullScreenButton={false}
- useNewFieldsApi={true}
maxDocFieldsDisplayed={50}
consumer="timeline"
totalHits={
@@ -210,7 +208,6 @@ const {
dataView,
dataViews,
setAppState: stateContainer.appState.update,
- useNewFieldsApi,
columns,
sort,
});
diff --git a/packages/kbn-unified-data-table/src/components/actions/columns.test.ts b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts
index 8850a7f8caf1c..370ea3b2a20de 100644
--- a/packages/kbn-unified-data-table/src/components/actions/columns.test.ts
+++ b/packages/kbn-unified-data-table/src/components/actions/columns.test.ts
@@ -25,7 +25,6 @@ function getStateColumnAction(
} as unknown as Capabilities,
dataView: dataViewMock,
dataViews: dataViewsMock,
- useNewFieldsApi: true,
setAppState,
columns: state.columns,
sort: state.sort,
diff --git a/packages/kbn-unified-data-table/src/components/actions/columns.ts b/packages/kbn-unified-data-table/src/components/actions/columns.ts
index 50682df14721c..97d00340a01f3 100644
--- a/packages/kbn-unified-data-table/src/components/actions/columns.ts
+++ b/packages/kbn-unified-data-table/src/components/actions/columns.ts
@@ -18,7 +18,6 @@ export function getStateColumnActions({
capabilities,
dataView,
dataViews,
- useNewFieldsApi,
setAppState,
columns,
sort,
@@ -28,7 +27,6 @@ export function getStateColumnActions({
capabilities: Capabilities;
dataView: DataView;
dataViews: DataViewsContract;
- useNewFieldsApi: boolean;
setAppState: (state: {
columns: string[];
sort?: string[][];
@@ -41,7 +39,7 @@ export function getStateColumnActions({
}) {
function onAddColumn(columnName: string) {
popularizeField(dataView, columnName, dataViews, capabilities);
- const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi);
+ const nextColumns = addColumn(columns || [], columnName);
const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort;
setAppState({ columns: nextColumns, sort: nextSort, settings });
}
@@ -49,7 +47,7 @@ export function getStateColumnActions({
function onRemoveColumn(columnName: string) {
popularizeField(dataView, columnName, dataViews, capabilities);
- const nextColumns = removeColumn(columns || [], columnName, useNewFieldsApi);
+ const nextColumns = removeColumn(columns || [], columnName);
// The state's sort property is an array of [sortByColumn,sortDirection]
const nextSort = sort && sort.length ? sort.filter((subArr) => subArr[0] !== columnName) : [];
@@ -98,32 +96,28 @@ export function getStateColumnActions({
* Helper function to provide a fallback to a single _source column if the given array of columns
* is empty, and removes _source if there are more than 1 columns given
* @param columns
- * @param useNewFieldsApi should a new fields API be used
*/
-function buildColumns(columns: string[], useNewFieldsApi = false) {
+function buildColumns(columns: string[]) {
if (columns.length > 1 && columns.indexOf('_source') !== -1) {
return columns.filter((col) => col !== '_source');
} else if (columns.length !== 0) {
return columns;
}
- return useNewFieldsApi ? [] : ['_source'];
+ return [];
}
-function addColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
+function addColumn(columns: string[], columnName: string) {
if (columns.includes(columnName)) {
return columns;
}
- return buildColumns([...columns, columnName], useNewFieldsApi);
+ return buildColumns([...columns, columnName]);
}
-function removeColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
+function removeColumn(columns: string[], columnName: string) {
if (!columns.includes(columnName)) {
return columns;
}
- return buildColumns(
- columns.filter((col) => col !== columnName),
- useNewFieldsApi
- );
+ return buildColumns(columns.filter((col) => col !== columnName));
}
function moveColumn(columns: string[], columnName: string, newIndex: number) {
diff --git a/packages/kbn-unified-data-table/src/components/data_table.test.tsx b/packages/kbn-unified-data-table/src/components/data_table.test.tsx
index 3ee4e5a9e7a13..c52d9377112b7 100644
--- a/packages/kbn-unified-data-table/src/components/data_table.test.tsx
+++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx
@@ -82,7 +82,6 @@ function getProps(): UnifiedDataTableProps {
settings: {},
showTimeCol: true,
sort: [],
- useNewFieldsApi: true,
services: {
fieldFormats: services.fieldFormats,
uiSettings: services.uiSettings,
@@ -124,7 +123,6 @@ const renderDataTable = async (props: Partial) => {
setSettings(state.settings);
}
}, []),
- useNewFieldsApi: true,
columns,
settings,
});
diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx
index 7f4297404c4e5..abaec0f6a98e3 100644
--- a/packages/kbn-unified-data-table/src/components/data_table.tsx
+++ b/packages/kbn-unified-data-table/src/components/data_table.tsx
@@ -217,10 +217,6 @@ export interface UnifiedDataTableProps {
* Current sort setting
*/
sort: SortOrder[];
- /**
- * How the data is fetched
- */
- useNewFieldsApi: boolean;
/**
* Manage pagination control
*/
@@ -454,7 +450,6 @@ export const UnifiedDataTable = ({
showTimeCol,
showFullScreenButton = true,
sort,
- useNewFieldsApi,
isSortEnabled = true,
isPaginationEnabled = true,
cellActionsTriggerId,
@@ -716,7 +711,6 @@ export const UnifiedDataTable = ({
getRenderCellValueFn({
dataView,
rows: displayedRows,
- useNewFieldsApi,
shouldShowFieldHandler,
closePopover: () => dataGridRef.current?.closeCellPopover(),
fieldFormats,
@@ -728,7 +722,6 @@ export const UnifiedDataTable = ({
[
dataView,
displayedRows,
- useNewFieldsApi,
shouldShowFieldHandler,
maxDocFieldsDisplayed,
fieldFormats,
diff --git a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx
index 31b1b42b7c6d0..f0c9ef46ebac3 100644
--- a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx
+++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx
@@ -22,7 +22,6 @@ describe('useColumns', () => {
dataViews: dataViewsMock,
setAppState: () => {},
columns: ['Time', 'message'],
- useNewFieldsApi: false,
};
test('should return valid result', () => {
@@ -37,12 +36,11 @@ describe('useColumns', () => {
expect(result.current.onSetColumns).toBeInstanceOf(Function);
});
- test('should skip _source column when useNewFieldsApi is set to true', () => {
+ test('should skip _source column', () => {
const { result } = renderHook(() => {
return useColumns({
...defaultProps,
columns: ['Time', '_source'],
- useNewFieldsApi: true,
});
});
diff --git a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts
index be4843eec2c3c..e5f9c629b4b76 100644
--- a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts
+++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.ts
@@ -19,7 +19,6 @@ export interface UseColumnsProps {
capabilities: Capabilities;
dataView: DataView;
dataViews: DataViewsContract;
- useNewFieldsApi: boolean;
setAppState: (state: {
columns: string[];
sort?: string[][];
@@ -36,20 +35,19 @@ export const useColumns = ({
dataView,
dataViews,
setAppState,
- useNewFieldsApi,
columns,
sort,
defaultOrder = 'desc',
settings,
}: UseColumnsProps) => {
- const [usedColumns, setUsedColumns] = useState(getColumns(columns, useNewFieldsApi));
+ const [usedColumns, setUsedColumns] = useState(getColumns(columns));
useEffect(() => {
- const nextColumns = getColumns(columns, useNewFieldsApi);
+ const nextColumns = getColumns(columns);
if (isEqual(usedColumns, nextColumns)) {
return;
}
setUsedColumns(nextColumns);
- }, [columns, useNewFieldsApi, usedColumns]);
+ }, [columns, usedColumns]);
const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo(
() =>
getStateColumnActions({
@@ -57,23 +55,12 @@ export const useColumns = ({
dataView,
dataViews,
setAppState,
- useNewFieldsApi,
columns: usedColumns,
sort,
defaultOrder,
settings,
}),
- [
- capabilities,
- dataView,
- dataViews,
- defaultOrder,
- setAppState,
- settings,
- sort,
- useNewFieldsApi,
- usedColumns,
- ]
+ [capabilities, dataView, dataViews, defaultOrder, setAppState, settings, sort, usedColumns]
);
return {
@@ -85,9 +72,9 @@ export const useColumns = ({
};
};
-function getColumns(columns: string[] | undefined, useNewFieldsApi: boolean) {
+function getColumns(columns: string[] | undefined) {
if (!columns) {
return [];
}
- return useNewFieldsApi ? columns.filter((col) => col !== '_source') : columns;
+ return columns.filter((col) => col !== '_source');
}
diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx
index 11636b9d1f761..0dfeb1f691e88 100644
--- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx
+++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx
@@ -113,7 +113,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsSource.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -139,7 +138,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsSource.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -166,7 +164,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFields.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: closePopoverMockFn,
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -196,7 +193,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: false,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -233,7 +229,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsSource.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -300,7 +295,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: false,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -341,7 +335,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: true,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -380,7 +373,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: true,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -418,7 +410,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFields.map(build),
- useNewFieldsApi: true,
shouldShowFieldHandler: (fieldName: string) => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -495,7 +486,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: true,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -536,7 +526,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows,
- useNewFieldsApi: true,
shouldShowFieldHandler: showFieldHandler,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -574,7 +563,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFieldsWithTopLevelObject.map(build),
- useNewFieldsApi: true,
shouldShowFieldHandler: () => false,
closePopover: closePopoverMockFn,
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -649,7 +637,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFieldsWithTopLevelObject.map(build),
- useNewFieldsApi: true,
shouldShowFieldHandler: () => false,
closePopover: closePopoverMockFn,
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -678,7 +665,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFieldsWithTopLevelObject.map(build),
- useNewFieldsApi: true,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -713,7 +699,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsSource.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -739,7 +724,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsSource.map(build),
- useNewFieldsApi: false,
shouldShowFieldHandler: () => false,
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
@@ -778,7 +762,6 @@ describe('Unified data table cell rendering', function () {
const DataTableCellValue = getRenderCellValueFn({
dataView: dataViewMock,
rows: rowsFieldsUnmapped.map(build),
- useNewFieldsApi: true,
shouldShowFieldHandler: (fieldName: string) => ['unmapped'].includes(fieldName),
closePopover: jest.fn(),
fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart,
diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
index 585feb675016c..f05499f7618b9 100644
--- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
+++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx
@@ -32,7 +32,6 @@ const IS_JEST_ENVIRONMENT = typeof jest !== 'undefined';
export const getRenderCellValueFn = ({
dataView,
rows,
- useNewFieldsApi,
shouldShowFieldHandler,
closePopover,
fieldFormats,
@@ -43,7 +42,6 @@ export const getRenderCellValueFn = ({
}: {
dataView: DataView;
rows: DataTableRecord[] | undefined;
- useNewFieldsApi: boolean;
shouldShowFieldHandler: ShouldShowFieldInTableHandler;
closePopover: () => void;
fieldFormats: FieldFormatsStart;
@@ -111,10 +109,7 @@ export const getRenderCellValueFn = ({
* this is used for legacy stuff like displaying products of our ecommerce dataset
*/
const useTopLevelObjectColumns = Boolean(
- useNewFieldsApi &&
- !field &&
- row?.raw.fields &&
- !(row.raw.fields as Record)[columnId]
+ !field && row?.raw.fields && !(row.raw.fields as Record)[columnId]
);
if (isDetails) {
diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
index 2d23903c34ea3..7d98764279016 100644
--- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
+++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
@@ -25,7 +25,7 @@ import {
import { ToolbarButton } from '@kbn/shared-ux-button-toolbar';
import { DataViewField, type FieldSpec } from '@kbn/data-views-plugin/common';
import { getDataViewFieldSubtypeMulti } from '@kbn/es-query/src/utils';
-import { FIELDS_LIMIT_SETTING, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils';
+import { FIELDS_LIMIT_SETTING } from '@kbn/discover-utils';
import { FieldList } from '../../components/field_list';
import { FieldListFilters } from '../../components/field_list_filters';
import { FieldListGrouped, type FieldListGroupedProps } from '../../components/field_list_grouped';
@@ -174,10 +174,6 @@ export const UnifiedFieldListSidebarComponent: React.FC {
const { dataViews, core } = services;
- const useNewFieldsApi = useMemo(
- () => !core.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE),
- [core.uiSettings]
- );
const [selectedFieldsState, setSelectedFieldsState] = useState(
INITIAL_SELECTED_FIELDS_RESULT
@@ -244,7 +240,6 @@ export const UnifiedFieldListSidebarComponent: React.FC {
if (
searchMode !== 'documents' ||
- !useNewFieldsApi ||
stateService.creationOptions.disableMultiFieldsGroupingByParent
) {
setMultiFieldsMap(undefined); // we don't have to calculate multifields in this case
@@ -257,7 +252,6 @@ export const UnifiedFieldListSidebarComponent: React.FC
fieldsMetadata,
} = services;
- const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]);
-
/**
* Context app state
*/
@@ -79,7 +77,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
dataViews,
- useNewFieldsApi,
setAppState,
columns: appState.columns,
sort: appState.sort,
@@ -110,7 +107,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
anchorId,
dataView,
appState,
- useNewFieldsApi,
});
/**
@@ -284,7 +280,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
{
defaultStepSize: 5,
predecessorCount: 10,
successorCount: 10,
- useNewFieldsApi: true,
isPaginationEnabled: false,
onAddColumn: () => {},
onRemoveColumn: () => {},
diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx
index ed54b4f8145e2..6108f4d3485fc 100644
--- a/src/plugins/discover/public/application/context/context_app_content.tsx
+++ b/src/plugins/discover/public/application/context/context_app_content.tsx
@@ -70,7 +70,6 @@ export interface ContextAppContentProps {
predecessorsStatus: LoadingStatus;
successorsStatus: LoadingStatus;
interceptedWarnings: SearchResponseWarning[];
- useNewFieldsApi: boolean;
setAppState: (newState: Partial) => void;
addFilter: DocViewFilterFn;
}
@@ -100,7 +99,6 @@ export function ContextAppContent({
predecessorsStatus,
successorsStatus,
interceptedWarnings,
- useNewFieldsApi,
setAppState,
addFilter,
}: ContextAppContentProps) {
@@ -225,7 +223,6 @@ export function ContextAppContent({
sort={sort as SortOrder[]}
isSortEnabled={false}
showTimeCol={showTimeCol}
- useNewFieldsApi={useNewFieldsApi}
isPaginationEnabled={false}
rowsPerPageState={getDefaultRowsPerPage(services.uiSettings)}
controlColumnIds={controlColumnIds}
diff --git a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx
index edffaa1c32530..171d3f8ec3abe 100644
--- a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx
+++ b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx
@@ -100,7 +100,6 @@ const initDefaults = (tieBreakerFields: string[], dataViewId = 'the-data-view-id
predecessorCount: 2,
successorCount: 2,
},
- useNewFieldsApi: false,
} as ContextAppFetchProps,
};
diff --git a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.tsx b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.tsx
index 2ab9eba2831d8..ebef8340cc391 100644
--- a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.tsx
+++ b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.tsx
@@ -37,15 +37,9 @@ export interface ContextAppFetchProps {
anchorId: string;
dataView: DataView;
appState: AppState;
- useNewFieldsApi: boolean;
}
-export function useContextAppFetch({
- anchorId,
- dataView,
- appState,
- useNewFieldsApi,
-}: ContextAppFetchProps) {
+export function useContextAppFetch({ anchorId, dataView, appState }: ContextAppFetchProps) {
const services = useDiscoverServices();
const { uiSettings: config, data, toastNotifications, filterManager } = services;
@@ -89,14 +83,7 @@ export function useContextAppFetch({
tieBreakerFieldName,
isTimeNanosBased: dataView.isTimeNanosBased(),
});
- const result = await fetchAnchor(
- anchorId,
- dataView,
- searchSource,
- sort,
- useNewFieldsApi,
- services
- );
+ const result = await fetchAnchor(anchorId, dataView, searchSource, sort, services);
setState({
anchor: result.anchorRow,
anchorInterceptedWarnings: result.interceptedWarnings,
@@ -118,7 +105,6 @@ export function useContextAppFetch({
dataView,
anchorId,
searchSource,
- useNewFieldsApi,
]);
const fetchSurroundingRows = useCallback(
@@ -146,7 +132,6 @@ export function useContextAppFetch({
count,
filters,
data,
- useNewFieldsApi,
services
)
: { rows: [], interceptedWarnings: undefined };
@@ -172,7 +157,6 @@ export function useContextAppFetch({
setState,
dataView,
toastNotifications,
- useNewFieldsApi,
data,
]
);
diff --git a/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap b/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap
index 972df33dfa37d..18ecc09da858f 100644
--- a/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap
+++ b/src/plugins/discover/public/application/context/services/__snapshots__/context.predecessors.test.ts.snap
@@ -2,6 +2,15 @@
exports[`context predecessors function fetchPredecessors should perform multiple queries until the expected hit count is returned 1`] = `
Array [
+ Array [
+ "fields",
+ Array [
+ Object {
+ "field": "*",
+ "include_unmapped": true,
+ },
+ ],
+ ],
Array [
"index",
Object {
diff --git a/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap b/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap
index 1f8623595b707..9d53999191d38 100644
--- a/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap
+++ b/src/plugins/discover/public/application/context/services/__snapshots__/context.successors.test.ts.snap
@@ -2,6 +2,15 @@
exports[`context successors function fetchSuccessors should perform multiple queries until the expected hit count is returned 1`] = `
Array [
+ Array [
+ "fields",
+ Array [
+ Object {
+ "field": "*",
+ "include_unmapped": true,
+ },
+ ],
+ ],
Array [
"index",
Object {
diff --git a/src/plugins/discover/public/application/context/services/__snapshots__/context.test.ts.snap b/src/plugins/discover/public/application/context/services/__snapshots__/context.test.ts.snap
index 48bbf85bb9314..24834c6abf40a 100644
--- a/src/plugins/discover/public/application/context/services/__snapshots__/context.test.ts.snap
+++ b/src/plugins/discover/public/application/context/services/__snapshots__/context.test.ts.snap
@@ -1,27 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`context api createSearchSource when useFieldsApi is false 1`] = `
-Object {
- "_source": Object {},
- "fields": Array [],
- "query": Object {
- "bool": Object {
- "filter": Array [],
- "must": Array [],
- "must_not": Array [],
- "should": Array [],
- },
- },
- "runtime_mappings": Object {},
- "script_fields": Object {},
- "stored_fields": Array [
- "*",
- ],
- "track_total_hits": false,
-}
-`;
-
-exports[`context api createSearchSource when useFieldsApi is true 1`] = `
+exports[`context api createSearchSource 1`] = `
Object {
"_source": false,
"fields": Array [
diff --git a/src/plugins/discover/public/application/context/services/anchor.test.ts b/src/plugins/discover/public/application/context/services/anchor.test.ts
index 8f6bcaf926ace..0d65f3ac93268 100644
--- a/src/plugins/discover/public/application/context/services/anchor.test.ts
+++ b/src/plugins/discover/public/application/context/services/anchor.test.ts
@@ -36,7 +36,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
expect(searchSourceStub.fetch$.calledOnce).toBe(true);
@@ -49,7 +48,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setParentSpy = searchSourceStub.setParent;
@@ -64,7 +62,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setFieldSpy = searchSourceStub.setField;
@@ -78,7 +75,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setVersionSpy = searchSourceStub.setField.withArgs('version');
@@ -93,7 +89,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setSizeSpy = searchSourceStub.setField.withArgs('size');
@@ -108,7 +103,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setQuerySpy = searchSourceStub.setField.withArgs('query');
@@ -134,7 +128,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(() => {
const setSortSpy = searchSourceStub.setField.withArgs('sort');
@@ -146,27 +139,8 @@ describe('context app', function () {
});
});
- it('should update search source correctly when useNewFieldsApi set to false', function () {
- const searchSource = updateSearchSource(
- savedSearchMock.searchSource,
- 'id',
- [],
- false,
- dataViewMock
- );
- const searchRequestBody = searchSource.getSearchRequestBody();
- expect(searchRequestBody._source).toBeInstanceOf(Object);
- expect(searchRequestBody.track_total_hits).toBe(false);
- });
-
- it('should update search source correctly when useNewFieldsApi set to true', function () {
- const searchSource = updateSearchSource(
- savedSearchMock.searchSource,
- 'id',
- [],
- true,
- dataViewMock
- );
+ it('should update search source correctly', function () {
+ const searchSource = updateSearchSource(savedSearchMock.searchSource, 'id', [], dataViewMock);
const searchRequestBody = searchSource.getSearchRequestBody();
expect(searchRequestBody._source).toBe(false);
expect(searchRequestBody.track_total_hits).toBe(false);
@@ -180,7 +154,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(
() => {
@@ -203,7 +176,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
discoverServiceMock
).then(({ anchorRow, interceptedWarnings }) => {
expect(anchorRow).toHaveProperty('raw._id', '1');
@@ -229,7 +201,6 @@ describe('context app', function () {
dataView,
searchSourceStub,
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- false,
services
).then(({ anchorRow, interceptedWarnings }) => {
expect(anchorRow).toHaveProperty('raw._id', '1');
@@ -239,28 +210,22 @@ describe('context app', function () {
});
});
- describe('useNewFields API', () => {
- beforeEach(() => {
- searchSourceStub = createSearchSourceStub([{ _id: 'hit1', _index: 't' }]);
- });
-
- it('should request fields if useNewFieldsApi set', function () {
- searchSourceStub._stubHits = [{ property1: 'value1' }, { property2: 'value2' }];
-
- return fetchAnchor(
- 'id',
- dataView,
- searchSourceStub,
- [{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
- true,
- discoverServiceMock
- ).then(() => {
- const setFieldsSpy = searchSourceStub.setField.withArgs('fields');
- const removeFieldsSpy = searchSourceStub.removeField.withArgs('fieldsFromSource');
- expect(setFieldsSpy.calledOnce).toBe(true);
- expect(removeFieldsSpy.calledOnce).toBe(true);
- expect(setFieldsSpy.firstCall.args[1]).toEqual([{ field: '*', include_unmapped: true }]);
- });
+ it('should request fields', function () {
+ searchSourceStub = createSearchSourceStub([{ _id: 'hit1', _index: 't' }]);
+ searchSourceStub._stubHits = [{ property1: 'value1' }, { property2: 'value2' }];
+
+ return fetchAnchor(
+ 'id',
+ dataView,
+ searchSourceStub,
+ [{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
+ discoverServiceMock
+ ).then(() => {
+ const setFieldsSpy = searchSourceStub.setField.withArgs('fields');
+ const removeFieldsSpy = searchSourceStub.removeField.withArgs('fieldsFromSource');
+ expect(setFieldsSpy.calledOnce).toBe(true);
+ expect(removeFieldsSpy.calledOnce).toBe(true);
+ expect(setFieldsSpy.firstCall.args[1]).toEqual([{ field: '*', include_unmapped: true }]);
});
});
});
diff --git a/src/plugins/discover/public/application/context/services/anchor.ts b/src/plugins/discover/public/application/context/services/anchor.ts
index ee5198a8b4100..d5a5cb0f013f4 100644
--- a/src/plugins/discover/public/application/context/services/anchor.ts
+++ b/src/plugins/discover/public/application/context/services/anchor.ts
@@ -23,7 +23,6 @@ export async function fetchAnchor(
dataView: DataView,
searchSource: ISearchSource,
sort: EsQuerySortValue[],
- useNewFieldsApi: boolean = false,
services: DiscoverServices
): Promise<{
anchorRow: DataTableRecord;
@@ -35,7 +34,7 @@ export async function fetchAnchor(
query: { query: '', language: 'kuery' },
});
- updateSearchSource(searchSource, anchorId, sort, useNewFieldsApi, dataView);
+ updateSearchSource(searchSource, anchorId, sort, dataView);
const adapter = new RequestAdapter();
const { rawResponse } = await lastValueFrom(
@@ -75,7 +74,6 @@ export function updateSearchSource(
searchSource: ISearchSource,
anchorId: string,
sort: EsQuerySortValue[],
- useNewFieldsApi: boolean,
dataView: DataView
) {
searchSource
@@ -97,9 +95,9 @@ export function updateSearchSource(
})
.setField('sort', sort)
.setField('trackTotalHits', false);
- if (useNewFieldsApi) {
- searchSource.removeField('fieldsFromSource');
- searchSource.setField('fields', [{ field: '*', include_unmapped: true }]);
- }
+
+ searchSource.removeField('fieldsFromSource');
+ searchSource.setField('fields', [{ field: '*', include_unmapped: true }]);
+
return searchSource;
}
diff --git a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts
index b0d102a62f6c4..f07f04214ada6 100644
--- a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts
+++ b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts
@@ -87,7 +87,6 @@ describe('context predecessors', function () {
size,
[],
dataPluginMock,
- false,
discoverServiceMock
);
};
@@ -204,7 +203,7 @@ describe('context predecessors', function () {
});
});
- describe('function fetchPredecessors with useNewFieldsApi set', function () {
+ describe('fetchPredecessors', function () {
beforeEach(() => {
mockSearchSource = createContextSearchSourceStub('@timestamp');
@@ -238,7 +237,6 @@ describe('context predecessors', function () {
size,
[],
dataPluginMock,
- true,
discoverServiceMock
);
};
diff --git a/src/plugins/discover/public/application/context/services/context.successors.test.ts b/src/plugins/discover/public/application/context/services/context.successors.test.ts
index 9543dd202c613..0756ff40d4f28 100644
--- a/src/plugins/discover/public/application/context/services/context.successors.test.ts
+++ b/src/plugins/discover/public/application/context/services/context.successors.test.ts
@@ -87,7 +87,6 @@ describe('context successors', function () {
size,
[],
dataPluginMock,
- false,
discoverServiceMock
);
};
@@ -205,7 +204,7 @@ describe('context successors', function () {
});
});
- describe('function fetchSuccessors with useNewFieldsApi set', function () {
+ describe('fetchSuccessors', function () {
beforeEach(() => {
mockSearchSource = createContextSearchSourceStub('@timestamp');
@@ -240,7 +239,6 @@ describe('context successors', function () {
size,
[],
dataPluginMock,
- true,
discoverServiceMock
);
};
@@ -312,7 +310,6 @@ describe('context successors', function () {
size,
[],
dataPluginMock,
- true,
{
...discoverServiceMock,
data: dataPluginMock,
diff --git a/src/plugins/discover/public/application/context/services/context.test.ts b/src/plugins/discover/public/application/context/services/context.test.ts
index d817a6c81e248..ce06b2c283564 100644
--- a/src/plugins/discover/public/application/context/services/context.test.ts
+++ b/src/plugins/discover/public/application/context/services/context.test.ts
@@ -12,14 +12,9 @@ import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
describe('context api', function () {
- test('createSearchSource when useFieldsApi is true', () => {
+ test('createSearchSource', () => {
const newSearchSource = createSearchSourceMock({ index: dataViewMock });
- const searchSource = updateSearchSource(newSearchSource, dataViewMock, [], true);
- expect(searchSource.getSearchRequestBody()).toMatchSnapshot();
- });
- test('createSearchSource when useFieldsApi is false', () => {
- const newSearchSource = createSearchSourceMock({ index: dataViewMock });
- const searchSource = updateSearchSource(newSearchSource, dataViewMock, [], false);
+ const searchSource = updateSearchSource(newSearchSource, dataViewMock, []);
expect(searchSource.getSearchRequestBody()).toMatchSnapshot();
});
});
diff --git a/src/plugins/discover/public/application/context/services/context.ts b/src/plugins/discover/public/application/context/services/context.ts
index 2c80b46eb25b8..68c86d35d7f04 100644
--- a/src/plugins/discover/public/application/context/services/context.ts
+++ b/src/plugins/discover/public/application/context/services/context.ts
@@ -41,7 +41,6 @@ const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map((days) => days * DAY_MILLIS
* @param {number} size - number of records to retrieve
* @param {Filter[]} filters - to apply in the elastic query
* @param {DataPublicPluginStart} data
- * @param {boolean} useNewFieldsApi
* @param {DiscoverServices} services
* @returns {Promise