From 850785ac50b6072027a3265eb2e2a1a50f01e505 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:35:48 -0600 Subject: [PATCH 01/12] Update ftr (8.x) (#206328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@types/selenium-webdriver](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/selenium-webdriver) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/selenium-webdriver)) | devDependencies | patch | [`^4.1.27` -> `^4.1.28`](https://renovatebot.com/diffs/npm/@types%2fselenium-webdriver/4.1.27/4.1.28) | | [chromedriver](https://togithub.com/giggio/node-chromedriver) | devDependencies | major | [`^131.0.1` -> `^132.0.0`](https://renovatebot.com/diffs/npm/chromedriver/131.0.1/132.0.0) | --- ### Release Notes
giggio/node-chromedriver (chromedriver) ### [`v132.0.0`](https://togithub.com/giggio/node-chromedriver/compare/131.0.5...132.0.0) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/131.0.5...132.0.0) ### [`v131.0.5`](https://togithub.com/giggio/node-chromedriver/compare/131.0.4...131.0.5) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/131.0.4...131.0.5) ### [`v131.0.4`](https://togithub.com/giggio/node-chromedriver/compare/131.0.3...131.0.4) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/131.0.3...131.0.4) ### [`v131.0.3`](https://togithub.com/giggio/node-chromedriver/compare/131.0.2...131.0.3) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/131.0.2...131.0.3) ### [`v131.0.2`](https://togithub.com/giggio/node-chromedriver/compare/131.0.1...131.0.2) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/131.0.1...131.0.2)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/renovatebot/renovate). Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 8fcc0e79e357c..b724d5376d3bb 100644 --- a/package.json +++ b/package.json @@ -1656,7 +1656,7 @@ "@types/resolve": "^1.20.1", "@types/scheduler": "^0.23.0", "@types/seedrandom": ">=2.0.0 <4.0.0", - "@types/selenium-webdriver": "^4.1.27", + "@types/selenium-webdriver": "^4.1.28", "@types/semver": "^7.5.8", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", @@ -1703,7 +1703,7 @@ "buildkite-test-collector": "^1.7.0", "callsites": "^3.1.0", "chance": "1.0.18", - "chromedriver": "^131.0.1", + "chromedriver": "^132.0.0", "clean-webpack-plugin": "^3.0.0", "cli-progress": "^3.12.0", "cli-table3": "^0.6.1", diff --git a/yarn.lock b/yarn.lock index 5729f35207eb5..e7dd30222fa72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12373,10 +12373,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@^4.1.27": - version "4.1.27" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.27.tgz#e08000d649df6f099b4099432bd2fece9f50ea7b" - integrity sha512-ALqsj8D7Swb6MnBQuAQ58J3KC3yh6fLGtAmpBmnZX8j+0kmP7NaLt56CuzBw2W2bXPrvHFTgn8iekOQFUKXEQA== +"@types/selenium-webdriver@^4.1.28": + version "4.1.28" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.28.tgz#7b4f3c50a67494f8fd6d396a2eaab7d9df1f9f34" + integrity sha512-Au7CXegiS7oapbB16zxPToY4Cjzi9UQQMf3W2ZZM8PigMLTGR3iUAHjPUTddyE5g1SBjT/qpmvlsAQLBfNAdKg== dependencies: "@types/node" "*" "@types/ws" "*" @@ -15273,10 +15273,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^131.0.1: - version "131.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-131.0.1.tgz#bfbf47f6c2ad7a65c154ff47d321bd8c33b52a77" - integrity sha512-LHRh+oaNU1WowJjAkWsviN8pTzQYJDbv/FvJyrQ7XhjKdIzVh/s3GV1iU7IjMTsxIQnBsTjx+9jWjzCWIXC7ug== +chromedriver@^132.0.0: + version "132.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-132.0.0.tgz#0eef414ccf906313f7685edb772fbbfa374e100d" + integrity sha512-jECVJjty5ypYKptQ/QCf8Q0Iq0qq2eW5x1WnYwlGCgmBBcwDH+XrdjFKc4mA3lFO1p3dpOUgTbayKCiGpMPBjg== dependencies: "@testim/chrome-version" "^1.1.4" axios "^1.7.4" From d733fd6d9be162962d591704a2f78a1a5c041355 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 23 Jan 2025 06:37:48 +1100 Subject: [PATCH 02/12] [8.x] Upgrade Node.js to 20.18.2 (#207431) (#207894) # Backport This will backport the following commits from `main` to `8.x`: - [Upgrade Node.js to 20.18.2 (#207431)](https://github.com/elastic/kibana/pull/207431) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Jon --- .../kibana-pointer-compression.yml | 4 ---- .node-version | 2 +- .nvmrc | 2 +- WORKSPACE.bazel | 12 ++++++------ package.json | 2 +- .../tasks/nodejs/extract_node_builds_task.test.ts | 14 -------------- src/dev/build/tasks/nodejs/node_download_info.ts | 3 ++- .../server/services/files/client_to_host.test.ts | 4 ++-- 8 files changed, 13 insertions(+), 30 deletions(-) diff --git a/.buildkite/pipeline-resource-definitions/kibana-pointer-compression.yml b/.buildkite/pipeline-resource-definitions/kibana-pointer-compression.yml index 5a23fc95d9971..838b53079f5ee 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-pointer-compression.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-pointer-compression.yml @@ -27,10 +27,6 @@ spec: pipeline_file: ".buildkite/pipelines/pointer_compression.yml" provider_settings: trigger_mode: none - schedules: - Daily run: - branch: main - cronline: "@daily" teams: kibana-operations: access_level: MANAGE_BUILD_AND_READ diff --git a/.node-version b/.node-version index b8e593f5210c8..0254b1e633c75 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.15.1 +20.18.2 diff --git a/.nvmrc b/.nvmrc index b8e593f5210c8..0254b1e633c75 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.15.1 +20.18.2 diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index c1bae6c549f52..4c347d053640f 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -22,13 +22,13 @@ load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install # Setup the Node.js toolchain for the architectures we want to support node_repositories( node_repositories = { - "20.15.1-darwin_amd64": ("node-v20.15.1-darwin-x64.tar.gz", "node-v20.15.1-darwin-x64", "f5379772ffae1404cfd1fcc8cf0c6c5971306b8fb2090d348019047306de39dc"), - "20.15.1-darwin_arm64": ("node-v20.15.1-darwin-arm64.tar.gz", "node-v20.15.1-darwin-arm64", "4743bc042f90ba5d9edf09403207290a9cdd2f6061bdccf7caaa0bbfd49f343e"), - "20.15.1-linux_arm64": ("node-v20.15.1-linux-arm64.tar.xz", "node-v20.15.1-linux-arm64", "c049d670df0c27ae2fd53446df79b6227ab23aff930e38daf0ab3da41c396db5"), - "20.15.1-linux_amd64": ("node-v20.15.1-linux-x64.tar.xz", "node-v20.15.1-linux-x64", "a854c291c7b775bedab54251e1e273cfee1adf1dba25435bc52305ef41f143ab"), - "20.15.1-windows_amd64": ("node-v20.15.1-win-x64.zip", "node-v20.15.1-win-x64", "ba6c3711e2c3d0638c5f7cea3c234553808a73c52a5962a6cdb47b5210b70b04"), + "20.18.2-darwin_amd64": ("node-v20.18.2-darwin-x64.tar.gz", "node-v20.18.2-darwin-x64", "00a16bb0a82a2ad5d00d66b466ae1afa678482283747c27e9bce96668f334744"), + "20.18.2-darwin_arm64": ("node-v20.18.2-darwin-arm64.tar.gz", "node-v20.18.2-darwin-arm64", "fa76d5b5340f14070ebaa88ef8faa28c1e9271502725e830cb52f0cf5b6493de"), + "20.18.2-linux_arm64": ("node-v20.18.2-linux-arm64.tar.xz", "node-v20.18.2-linux-arm64", "1b4b1745ef7b6d342ddf998352438cfc61dbfcdf0895c9db7e9f1d8a427815d2"), + "20.18.2-linux_amd64": ("node-v20.18.2-linux-x64.tar.xz", "node-v20.18.2-linux-x64", "1a6e1fbd768437e130eac1a54c5535736d6992df700c09a6ce58f22040d6a34c"), + "20.18.2-windows_amd64": ("node-v20.18.2-win-x64.zip", "node-v20.18.2-win-x64", "ed790b94570518a7dce67b62485e16bc4bffecee4ec3b6df35ed220ae91117a5"), }, - node_version = "20.15.1", + node_version = "20.18.2", node_urls = [ "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v{version}/{filename}", ], diff --git a/package.json b/package.json index b724d5376d3bb..1161f29eb265a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "url": "https://github.com/elastic/kibana.git" }, "engines": { - "node": "20.15.1", + "node": "20.18.2", "yarn": "^1.22.19" }, "resolutions": { diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts index 82a46f6aab626..b58076dde7342 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts @@ -143,20 +143,6 @@ it('runs expected fs operations', async () => { "strip": 1, }, ], - Array [ - /.node_binaries///linux-x64/download/node-v-linux-x64.tar.gz, - /.node_binaries///linux-x64/extract, - Object { - "strip": 1, - }, - ], - Array [ - /.node_binaries///linux-arm64/download/node-v-linux-arm64.tar.gz, - /.node_binaries///linux-arm64/extract, - Object { - "strip": 1, - }, - ], Array [ /.node_binaries///linux-arm64/download/node-v-linux-arm64.tar.gz, /.node_binaries///linux-arm64/extract, diff --git a/src/dev/build/tasks/nodejs/node_download_info.ts b/src/dev/build/tasks/nodejs/node_download_info.ts index c9bda93e21c5d..95ea818e39117 100644 --- a/src/dev/build/tasks/nodejs/node_download_info.ts +++ b/src/dev/build/tasks/nodejs/node_download_info.ts @@ -22,7 +22,8 @@ export function getNodeDownloadInfo(config: Config, platform: Platform) { } else { variants = ['glibc-217']; } - if (platform.isServerless()) variants.push('pointer-compression'); + // disabled, see https://github.com/nodejs/node/issues/54531 + // if (platform.isServerless()) variants.push('pointer-compression'); } return variants.map((variant) => { diff --git a/x-pack/platform/plugins/shared/fleet/server/services/files/client_to_host.test.ts b/x-pack/platform/plugins/shared/fleet/server/services/files/client_to_host.test.ts index 1068f34366970..7f864a2536a4a 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/files/client_to_host.test.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/files/client_to_host.test.ts @@ -197,7 +197,7 @@ describe('FleetToHostFilesClient', () => { it('should error if `agentIds` is empty', async () => { await expect(getFleetFilesInstance().create(fileReadable, [])).rejects.toThrow( - 'FleetFilesClientError: Missing agentIds!' + 'Missing agentIds!' ); }); @@ -218,7 +218,7 @@ describe('FleetToHostFilesClient', () => { esFile.data.hash = undefined; await expect(getFleetFilesInstance().create(fileReadable, ['123'])).rejects.toThrow( - 'FleetFilesClientError: File hash was not generated!' + 'File hash was not generated!' ); }); }); From 19b8dc9ad224a1d972ddf503d8f80586809b740e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 23 Jan 2025 06:38:51 +1100 Subject: [PATCH 03/12] [8.x] [Security Solution] [Security Assistant] Fixes Security Assistant accessibility (a11y) issues (#207122) (#207895) # Backport This will backport the following commits from `main` to `8.x`: - [[Security Solution] [Security Assistant] Fixes Security Assistant accessibility (a11y) issues (#207122)](https://github.com/elastic/kibana/pull/207122) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Andrew Macri --- .../get_anonymization_tooltip/index.test.ts | 42 +++++++ .../get_anonymization_tooltip/index.ts | 22 ++++ .../assistant/assistant_header/index.test.tsx | 109 +++++++++++++++++- .../impl/assistant/assistant_header/index.tsx | 19 +-- .../assistant_header/translations.ts | 14 +++ .../settings_context_menu.test.tsx | 25 ++++ .../settings_context_menu.tsx | 3 +- .../settings_context_menu/translations.ts | 15 +++ 8 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.test.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.test.tsx create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/translations.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.test.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.test.ts new file mode 100644 index 0000000000000..35bc314abc813 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { getAnonymizationTooltip } from '.'; +import { + SHOW_ANONYMIZED, + SHOW_REAL_VALUES, + THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS, +} from '../translations'; + +describe('getAnonymizationTooltip', () => { + it('returns the expected tooltip when conversationHasReplacements is false', () => { + const result = getAnonymizationTooltip({ + conversationHasReplacements: false, // <-- false + showAnonymizedValuesChecked: false, + }); + + expect(result).toBe(THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS); + }); + + it('returns SHOW_REAL_VALUES when showAnonymizedValuesChecked is true', () => { + const result = getAnonymizationTooltip({ + conversationHasReplacements: true, + showAnonymizedValuesChecked: true, // <-- true + }); + + expect(result).toBe(SHOW_REAL_VALUES); + }); + + it('returns SHOW_ANONYMIZED when showAnonymizedValuesChecked is false', () => { + const result = getAnonymizationTooltip({ + conversationHasReplacements: true, + showAnonymizedValuesChecked: false, // <-- false + }); + + expect(result).toBe(SHOW_ANONYMIZED); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.ts new file mode 100644 index 0000000000000..6534c6f8b0302 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/get_anonymization_tooltip/index.ts @@ -0,0 +1,22 @@ +/* + * 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 * as i18n from '../translations'; + +export const getAnonymizationTooltip = ({ + conversationHasReplacements, + showAnonymizedValuesChecked, +}: { + conversationHasReplacements: boolean; + showAnonymizedValuesChecked: boolean; +}): string => { + if (!conversationHasReplacements) { + return i18n.THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS; + } + + return showAnonymizedValuesChecked ? i18n.SHOW_REAL_VALUES : i18n.SHOW_ANONYMIZED; +}; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx index fa358b26c2c3a..f91de230f7fb3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.test.tsx @@ -6,12 +6,20 @@ */ import React from 'react'; -import { act, fireEvent, render } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; + import { AssistantHeader } from '.'; import { TestProviders } from '../../mock/test_providers/test_providers'; import { alertConvo, emptyWelcomeConvo, welcomeConvo } from '../../mock/conversation'; import { useLoadConnectors } from '../../connectorland/use_load_connectors'; import { mockConnectors } from '../../mock/connectors'; +import { + CLOSE, + SHOW_ANONYMIZED, + SHOW_REAL_VALUES, + THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS, +} from './translations'; const onConversationSelected = jest.fn(); const mockConversations = { @@ -139,4 +147,103 @@ describe('AssistantHeader', () => { cTitle: alertConvo.title, }); }); + + it('renders an accessible close button icon', () => { + const onCloseFlyout = jest.fn(); // required to render the close button + + render(, { + wrapper: TestProviders, + }); + + expect(screen.getByRole('button', { name: CLOSE })).toBeInTheDocument(); + }); + + it('disables the anonymization toggle button when there are NO replacements', () => { + render( + , + { + wrapper: TestProviders, + } + ); + + expect(screen.getByTestId('showAnonymizedValues')).toBeDisabled(); + }); + + it('displays the expected anonymization toggle button tooltip when there are NO replacements', async () => { + render( + , + { + wrapper: TestProviders, + } + ); + + await userEvent.hover(screen.getByTestId('showAnonymizedValues'), { + pointerEventsCheck: PointerEventsCheckLevel.Never, + }); + + await waitFor(() => { + expect(screen.getByTestId('showAnonymizedValuesTooltip')).toHaveTextContent( + THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS + ); + }); + }); + + it('enables the anonymization toggle button when there are replacements', () => { + render( + , // <-- conversation with replacements + { + wrapper: TestProviders, + } + ); + + expect(screen.getByTestId('showAnonymizedValues')).toBeEnabled(); + }); + + it('displays the SHOW_ANONYMIZED toggle button tooltip when there are replacements and showAnonymizedValues is false', async () => { + render( + , + { + wrapper: TestProviders, + } + ); + + await userEvent.hover(screen.getByTestId('showAnonymizedValues'), { + pointerEventsCheck: PointerEventsCheckLevel.Never, + }); + + await waitFor(() => { + expect(screen.getByTestId('showAnonymizedValuesTooltip')).toHaveTextContent(SHOW_ANONYMIZED); + }); + }); + + it('displays the SHOW_REAL_VALUES toggle button tooltip when there are replacements and showAnonymizedValues is true', async () => { + render( + , + { + wrapper: TestProviders, + } + ); + + await userEvent.hover(screen.getByTestId('showAnonymizedValues'), { + pointerEventsCheck: PointerEventsCheckLevel.Never, + }); + + await waitFor(() => { + expect(screen.getByTestId('showAnonymizedValuesTooltip')).toHaveTextContent(SHOW_REAL_VALUES); + }); + }); }); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx index 406ef8be16c73..8c3b866ee128b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/index.tsx @@ -26,6 +26,7 @@ import { FlyoutNavigation } from '../assistant_overlay/flyout_navigation'; import { AssistantSettingsModal } from '../settings/assistant_settings_modal'; import * as i18n from './translations'; import { AIConnector } from '../../connectorland/connector_selector'; +import { getAnonymizationTooltip } from './get_anonymization_tooltip'; import { SettingsContextMenu } from '../settings/settings_context_menu/settings_context_menu'; interface OwnProps { @@ -102,6 +103,12 @@ export const AssistantHeader: React.FC = ({ [onConversationSelected] ); + const conversationHasReplacements = !isEmpty(selectedConversation?.replacements); + const anonymizationTooltip = getAnonymizationTooltip({ + conversationHasReplacements, + showAnonymizedValuesChecked, + }); + return ( <> = ({ {onCloseFlyout && ( = ({ = ({ display="base" data-test-subj="showAnonymizedValues" isSelected={showAnonymizedValuesChecked} - aria-label={ - showAnonymizedValuesChecked ? i18n.SHOW_ANONYMIZED : i18n.SHOW_REAL_VALUES - } + aria-label={anonymizationTooltip} iconType={showAnonymizedValuesChecked ? 'eye' : 'eyeClosed'} onClick={onToggleShowAnonymizedValues} - isDisabled={isEmpty(selectedConversation?.replacements)} + disabled={!conversationHasReplacements} /> diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/translations.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/translations.ts index e4f23e0970eb0..7d105e1ee69a6 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/translations.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_header/translations.ts @@ -21,6 +21,13 @@ export const ANONYMIZATION = i18n.translate( } ); +export const CLOSE = i18n.translate( + 'xpack.elasticAssistant.assistant.assistantHeader.closeButtonLabel', + { + defaultMessage: 'Close', + } +); + export const KNOWLEDGE_BASE = i18n.translate( 'xpack.elasticAssistant.assistant.settings.knowledgeBase', { @@ -56,6 +63,13 @@ export const SHOW_REAL_VALUES = i18n.translate( } ); +export const THIS_CONVERSATION_DOES_NOT_INCLUDE_ANONYMIZED_FIELDS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.thisConversationDoesNotIncludeAnonymizedFieldsTooltip', + { + defaultMessage: 'This conversation does not include anonymized fields', + } +); + export const CANCEL_BUTTON_TEXT = i18n.translate( 'xpack.elasticAssistant.assistant.resetConversationModal.cancelButtonText', { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.test.tsx new file mode 100644 index 0000000000000..c43869474bd17 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { SettingsContextMenu } from './settings_context_menu'; +import { AI_ASSISTANT_MENU } from './translations'; + +describe('SettingsContextMenu', () => { + it('renders an accessible menu button icon', () => { + render( + + + + ); + + expect(screen.getByRole('button', { name: AI_ASSISTANT_MENU })).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx index 7b55e994b47ad..4282955716c7a 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/settings_context_menu/settings_context_menu.tsx @@ -24,6 +24,7 @@ import { useAssistantContext } from '../../../..'; import * as i18n from '../../assistant_header/translations'; import { AlertsSettingsModal } from '../alerts_settings/alerts_settings_modal'; import { KNOWLEDGE_BASE_TAB } from '../const'; +import { AI_ASSISTANT_MENU } from './translations'; interface Params { isDisabled?: boolean; @@ -168,7 +169,7 @@ export const SettingsContextMenu: React.FC = React.memo( button={ Date: Thu, 23 Jan 2025 06:48:52 +1100 Subject: [PATCH 04/12] [8.x] [ResponseOps] Fix editing alerts filter for multi-consumer rule types on serverless (#206848) (#207898) # Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps] Fix editing alerts filter for multi-consumer rule types on serverless (#206848)](https://github.com/elastic/kibana/pull/206848) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Zacqary Adam Xeper --- .../src/utils/has_fields_for_aad.test.ts | 28 +------------------ .../rule_form/src/utils/has_fields_for_aad.ts | 7 ----- .../sections/rule_form/rule_form.tsx | 17 +---------- 3 files changed, 2 insertions(+), 50 deletions(-) diff --git a/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.test.ts b/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.test.ts index 9585d7a83b49b..85204661edda2 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.test.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { AlertConsumers, ES_QUERY_ID } from '@kbn/rule-data-utils'; +import { AlertConsumers } from '@kbn/rule-data-utils'; import { RuleTypeWithDescription } from '../common/types'; import { hasFieldsForAad } from './has_fields_for_aad'; @@ -49,30 +49,4 @@ describe('hasFieldsForAad', () => { expect(hasFields).toBeTruthy(); }); - - test('should return true if it is a multi-consumer rule and valid consumer contains it', () => { - const hasFields = hasFieldsForAad({ - ruleType: { - hasFieldsForAAD: true, - id: ES_QUERY_ID, - } as RuleTypeWithDescription, - consumer: 'stackAlerts', - validConsumers: ['stackAlerts'], - }); - - expect(hasFields).toBeTruthy(); - }); - - test('should return false if it is a multi-consumer rule and valid consumer does not contain it', () => { - const hasFields = hasFieldsForAad({ - ruleType: { - hasFieldsForAAD: true, - id: ES_QUERY_ID, - } as RuleTypeWithDescription, - consumer: 'stackAlerts', - validConsumers: ['logs'], - }); - - expect(hasFields).toBeFalsy(); - }); }); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.ts b/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.ts index 785db5f9fc18d..c46a96696d2e3 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/utils/has_fields_for_aad.ts @@ -9,7 +9,6 @@ import { AlertConsumers, RuleCreationValidConsumer } from '@kbn/rule-data-utils'; import { RuleTypeWithDescription } from '../common/types'; -import { DEFAULT_VALID_CONSUMERS, MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; export const hasFieldsForAad = ({ ruleType, @@ -26,11 +25,5 @@ export const hasFieldsForAad = ({ ruleType.hasAlertsMappings : false; - if (MULTI_CONSUMER_RULE_TYPE_IDS.includes(ruleType.id)) { - return !!( - (validConsumers || DEFAULT_VALID_CONSUMERS).includes(consumer as RuleCreationValidConsumer) && - hasAlertHasData - ); - } return !!hasAlertHasData; }; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 665dd93325c2b..b0817d054b643 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -671,23 +671,8 @@ export const RuleForm = ({ selectedRuleType.hasAlertsMappings : false; - if (MULTI_CONSUMER_RULE_TYPE_IDS.includes(rule?.ruleTypeId ?? '')) { - // Use selectedConsumer when creating a new rule, existing rule consumer when editing - const ruleConsumer = initialSelectedConsumer ? selectedConsumer : rule.consumer; - return ( - (validConsumers || VALID_CONSUMERS).includes(ruleConsumer as RuleCreationValidConsumer) && - hasAlertHasData - ); - } return hasAlertHasData; - }, [ - rule?.ruleTypeId, - initialSelectedConsumer, - rule.consumer, - selectedConsumer, - selectedRuleType, - validConsumers, - ]); + }, [selectedRuleType]); const ruleTypeDetails = ( <> From e87c6e662f9d573c9b34064465df65a50021db0b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 22 Jan 2025 21:16:58 +0100 Subject: [PATCH 05/12] =?UTF-8?q?[8.x]=20=F0=9F=8C=8A=20Fix=20MKI=20tests?= =?UTF-8?q?=20(#207397)=20(#207900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [🌊 Fix MKI tests (#207397)](https://github.com/elastic/kibana/pull/207397) --- .../packages/utils_server/es/storage/index.ts | 8 ++ .../es/storage/index_adapter/index.ts | 33 ++++++++ .../integration_tests/index.test.ts | 40 +++++++++ .../server/lib/streams/assets/asset_client.ts | 4 + .../streams/server/lib/streams/client.ts | 3 + .../observability/streams/assets/dashboard.ts | 83 ++----------------- 6 files changed, 96 insertions(+), 75 deletions(-) diff --git a/x-pack/solutions/observability/packages/utils_server/es/storage/index.ts b/x-pack/solutions/observability/packages/utils_server/es/storage/index.ts index c958827c1dec2..fde2e0fa966b1 100644 --- a/x-pack/solutions/observability/packages/utils_server/es/storage/index.ts +++ b/x-pack/solutions/observability/packages/utils_server/es/storage/index.ts @@ -67,6 +67,11 @@ export interface StorageClientDeleteResponse { result: Extract; } +export interface StorageClientCleanResponse { + acknowledged: boolean; + result: Extract; +} + export type StorageClientIndexRequest = Omit< IndexRequest>, 'index' @@ -96,6 +101,8 @@ export type StorageClientDelete = ( request: StorageClientDeleteRequest ) => Promise; +export type StorageClientClean = () => Promise; + export type StorageClientGet = ( request: StorageClientGetRequest ) => Promise>>; @@ -107,6 +114,7 @@ export interface IStorageClient; index: StorageClientIndex; delete: StorageClientDelete; + clean: StorageClientClean; get: StorageClientGet; existsIndex: StorageClientExistsIndex; } diff --git a/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/index.ts b/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/index.ts index 8674199368ac3..ac35a0aaf3dad 100644 --- a/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/index.ts +++ b/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/index.ts @@ -32,6 +32,8 @@ import { StorageClientExistsIndex, StorageDocumentOf, StorageClientSearchResponse, + StorageClientClean, + StorageClientCleanResponse, } from '..'; import { getSchemaVersion } from '../get_schema_version'; import { StorageMappingProperty } from '../types'; @@ -446,6 +448,36 @@ export class StorageIndexAdapter }); }; + private clean: StorageClientClean = async (): Promise => { + const allIndices = await this.getExistingIndices(); + const hasIndices = Object.keys(allIndices).length > 0; + // Delete all indices + await Promise.all( + Object.keys(allIndices).map((index) => + wrapEsCall( + this.esClient.indices.delete({ + index, + }) + ) + ) + ); + // Delete the index template + const template = await this.getExistingIndexTemplate(); + const hasTemplate = !!template; + if (template) { + await wrapEsCall( + this.esClient.indices.deleteIndexTemplate({ + name: getIndexTemplateName(this.storage.name), + }) + ); + } + + return { + acknowledged: true, + result: hasIndices || hasTemplate ? 'deleted' : 'noop', + }; + }; + private delete: StorageClientDelete = async ({ id, refresh = 'wait_for', @@ -546,6 +578,7 @@ export class StorageIndexAdapter return { bulk: this.bulk, delete: this.delete, + clean: this.clean, index: this.index, search: this.search, get: this.get, diff --git a/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/integration_tests/index.test.ts b/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/integration_tests/index.test.ts index 811c07a907670..3b94695192684 100644 --- a/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/integration_tests/index.test.ts +++ b/x-pack/solutions/observability/packages/utils_server/es/storage/index_adapter/integration_tests/index.test.ts @@ -178,6 +178,10 @@ describe('StorageIndexAdapter', () => { }); }); + it('deletes the document', async () => { + await verifyClean(); + }); + // FLAKY: https://github.com/elastic/kibana/issues/206482 // FLAKY: https://github.com/elastic/kibana/issues/206483 describe.skip('after rolling over the index manually and indexing the same document', () => { @@ -316,6 +320,10 @@ describe('StorageIndexAdapter', () => { it('deletes the document from the rolled over index', async () => { await verifyDocumentDeletedInRolledOverIndex(); }); + + it('deletes the documents', async () => { + await verifyClean(); + }); }); }); @@ -349,6 +357,10 @@ describe('StorageIndexAdapter', () => { expect(getIndicesResponse[writeIndexName].mappings?._meta?.version).toEqual('next_version'); }); + + it('deletes the documents', async () => { + await verifyClean(); + }); }); describe('when writing/bootstrapping with an existing, incompatible index', () => { @@ -387,6 +399,10 @@ describe('StorageIndexAdapter', () => { illegal_argument_exception: mapper [foo] cannot be changed from type [keyword] to [text]" `); }); + + it('deletes the documents', async () => { + await verifyClean(); + }); }); function createStorageIndexAdapter( @@ -567,4 +583,28 @@ describe('StorageIndexAdapter', () => { }, }); } + + async function verifyClean() { + await client.clean(); + + // verify that the index template is removed + const templates = await esClient.indices + .getIndexTemplate({ + name: TEST_INDEX_NAME, + }) + .catch((error) => { + if (isResponseError(error) && error.statusCode === 404) { + return { index_templates: [] }; + } + throw error; + }); + + expect(templates.index_templates).toEqual([]); + + // verify that the backing indices are removed + const indices = await esClient.indices.get({ + index: `${TEST_INDEX_NAME}*`, + }); + expect(Object.keys(indices)).toEqual([]); + } }); diff --git a/x-pack/solutions/observability/plugins/streams/server/lib/streams/assets/asset_client.ts b/x-pack/solutions/observability/plugins/streams/server/lib/streams/assets/asset_client.ts index b5713fe176473..1bc1385a081b3 100644 --- a/x-pack/solutions/observability/plugins/streams/server/lib/streams/assets/asset_client.ts +++ b/x-pack/solutions/observability/plugins/streams/server/lib/streams/assets/asset_client.ts @@ -177,6 +177,10 @@ export class AssetClient { await this.clients.storageClient.delete({ id }); } + async clean() { + await this.clients.storageClient.clean(); + } + async getAssetIds({ entityId, entityType, diff --git a/x-pack/solutions/observability/plugins/streams/server/lib/streams/client.ts b/x-pack/solutions/observability/plugins/streams/server/lib/streams/client.ts index 970dcc3b7497a..92db157438c03 100644 --- a/x-pack/solutions/observability/plugins/streams/server/lib/streams/client.ts +++ b/x-pack/solutions/observability/plugins/streams/server/lib/streams/client.ts @@ -143,6 +143,9 @@ export class StreamsClient { await this.deleteStreamFromDefinition(definition); + const { assetClient, storageClient } = this.dependencies; + await Promise.all([assetClient.clean(), storageClient.clean()]); + return { acknowledged: true, result: 'deleted' }; } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/streams/assets/dashboard.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/streams/assets/dashboard.ts index 6b1215548b4bf..14cd196bf26ae 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/streams/assets/dashboard.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/streams/assets/dashboard.ts @@ -96,19 +96,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(response.status).to.be(200); } - async function deleteAssetIndices() { - const concreteIndices = await esClient.indices.resolveIndex({ - name: '.kibana_streams_assets*', - }); - - if (concreteIndices.indices.length) { - await esClient.indices.delete({ - index: concreteIndices.indices.map((index) => index.name), - }); - } - } - - describe('Asset links', () => { + describe('Asset links', function () { before(async () => { apiClient = await createStreamsRepositoryAdminClient(roleScopedSupertest); await enableStreams(apiClient); @@ -121,28 +109,6 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { after(async () => { await disableStreams(apiClient); - - await deleteAssetIndices(); - }); - - describe('without writing', () => { - it('creates no indices initially', async () => { - const exists = await esClient.indices.exists({ index: '.kibana_streams_assets' }); - - expect(exists).to.eql(false); - }); - - it('creates no indices after reading the assets', async () => { - const response = await apiClient.fetch('GET /api/streams/{id}/dashboards', { - params: { path: { id: 'logs' } }, - }); - - expect(response.status).to.be(200); - - const exists = await esClient.indices.exists({ index: '.kibana_streams_assets' }); - - expect(exists).to.eql(false); - }); }); describe('after linking a dashboard', () => { @@ -157,12 +123,6 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { await unlinkDashboard(SEARCH_DASHBOARD_ID); }); - it('creates the index', async () => { - const exists = await esClient.indices.exists({ index: '.kibana_streams_assets' }); - - expect(exists).to.be(true); - }); - it('lists the dashboard in the stream response', async () => { const response = await apiClient.fetch('GET /api/streams/{id}', { params: { path: { id: 'logs' } }, @@ -183,54 +143,27 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(response.body.dashboards.length).to.eql(1); }); - describe('after manually rolling over the index and relinking the dashboard', () => { + describe('after disabling', () => { before(async () => { - await esClient.indices.updateAliases({ - actions: [ - { - add: { - index: `.kibana_streams_assets-000001`, - alias: `.kibana_streams_assets`, - is_write_index: false, - }, - }, - ], - }); - - await esClient.indices.create({ - index: `.kibana_streams_assets-000002`, - }); - - await unlinkDashboard(SEARCH_DASHBOARD_ID); - await linkDashboard(SEARCH_DASHBOARD_ID); + // disabling and re-enabling streams wipes the asset links + await disableStreams(apiClient); + await enableStreams(apiClient); }); - it('there are no duplicates', async () => { + it('dropped all dashboards', async () => { const response = await apiClient.fetch('GET /api/streams/{id}/dashboards', { params: { path: { id: 'logs' } }, }); expect(response.status).to.eql(200); - expect(response.body.dashboards.length).to.eql(1); - - const esResponse = await esClient.search({ - index: `.kibana_streams_assets`, - }); - - expect(esResponse.hits.hits.length).to.eql(1); + expect(response.body.dashboards.length).to.eql(0); }); - }); - - describe('after deleting the indices and relinking the dashboard', () => { - before(async () => { - await deleteAssetIndices(); + it('recovers on write and lists the linked dashboard ', async () => { await unlinkDashboard(SEARCH_DASHBOARD_ID); await linkDashboard(SEARCH_DASHBOARD_ID); - }); - it('recovers on write and lists the linked dashboard ', async () => { const response = await apiClient.fetch('GET /api/streams/{id}/dashboards', { params: { path: { id: 'logs' } }, }); From e69e7837242393a437b4cd212dbb5bb8e8da79a9 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Wed, 22 Jan 2025 21:18:33 +0100 Subject: [PATCH 06/12] [8.x] [Security Solution] Show deprecated bulk endpoints in Upgrade Assistant (#207091) (#207882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[Security Solution] Show deprecated bulk endpoints in Upgrade Assistant (#207091)](https://github.com/elastic/kibana/pull/207091) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) --- docs/upgrade-notes.asciidoc | 36 ++++++++ .../shared/kbn-doc-links/src/get_doc_links.ts | 1 + .../shared/kbn-doc-links/src/types.ts | 1 + .../security_solution/common/constants.ts | 1 + .../rule_management/api/api.ts | 28 +++---- .../routes/__mocks__/request_responses.ts | 5 +- .../rule_management/api/register_routes.ts | 19 +++-- .../api/rules/bulk_create_rules/route.test.ts | 5 +- .../api/rules/bulk_create_rules/route.ts | 24 +++++- .../api/rules/bulk_delete_rules/route.test.ts | 5 +- .../api/rules/bulk_delete_rules/route.ts | 40 ++++++++- .../api/rules/bulk_patch_rules/route.test.ts | 5 +- .../api/rules/bulk_patch_rules/route.ts | 24 +++++- .../api/rules/bulk_update_rules/route.test.ts | 5 +- .../api/rules/bulk_update_rules/route.ts | 24 +++++- .../api/rules/import_rules/route.ts | 4 +- .../security_solution/server/routes/index.ts | 2 +- .../import_rules.ts | 2 +- .../import_rules_with_overwrite.ts | 12 +-- .../import_connectors.ts | 20 ++--- .../import_export_rules.ts | 13 +-- .../import_rules.ts | 82 +++++++++---------- .../import_rules_ess.ts | 16 ++-- .../import_rules_with_overwrite.ts | 12 +-- 24 files changed, 260 insertions(+), 126 deletions(-) diff --git a/docs/upgrade-notes.asciidoc b/docs/upgrade-notes.asciidoc index 6e7c73bd1dd06..0b7f9d7a3b6f0 100644 --- a/docs/upgrade-notes.asciidoc +++ b/docs/upgrade-notes.asciidoc @@ -49,6 +49,42 @@ For Elastic Security release information, refer to {security-guide}/release-note [float] ==== Kibana APIs +[discrete] +[[breaking-207091]] +.Removed legacy security rules bulk endpoints (9.0.0) +[%collapsible] +==== +*Details* + +-- +* `POST /api/detection_engine/rules/_bulk_create` has been replaced by `POST /api/detection_engine/rules/_import` +* `PUT /api/detection_engine/rules/_bulk_update` has been replaced by `POST /api/detection_engine/rules/_bulk_action` +* `PATCH /api/detection_engine/rules/_bulk_update has been replaced by `POST /api/detection_engine/rules/_bulk_action` +* `DELETE /api/detection_engine/rules/_bulk_delete` has been replaced by `POST /api/detection_engine/rules/_bulk_action` +* `POST api/detection_engine/rules/_bulk_delete` has been replaced by `POST /api/detection_engine/rules/_bulk_action` +-- +These changes were introduced in {kibana-pull}197422[#197422]. + +*Impact* + +Deprecated endpoints will fail with a 404 status code starting from version 9.0.0 + +*Action* + +-- +Update your implementations to use the new endpoints: + +* **For bulk creation of rules:** + - Use `POST /api/detection_engine/rules/_import` (link:{api-kibana}/operation/operation-importrules[API documentation]) to create multiple rules along with their associated entities (for example, exceptions and action connectors). + - Alternatively, create rules individually using `POST /api/detection_engine/rules` (link:{api-kibana}/operation/operation-createrule[API documentation]). + +* **For bulk updates of rules:** + - Use `POST /api/detection_engine/rules/_bulk_action` (link:{api-kibana}/operation/operation-performrulesbulkaction[API documentation]) to update fields in multiple rules simultaneously. + - Alternatively, update rules individually using `PUT /api/detection_engine/rules` (link:{api-kibana}/operation/operation-updaterule[API documentation]). + +* **For bulk deletion of rules:** + - Use `POST /api/detection_engine/rules/_bulk_action` (link:{api-kibana}/operation/operation-performrulesbulkaction[API documentation]) to delete multiple rules by IDs or query. + - Alternatively, delete rules individually using `DELETE /api/detection_engine/rules` (link:{api-kibana}/operation/operation-deleterule[API documentation]). +-- +==== + [discrete] [[breaking-199598]] .Remove deprecated endpoint management endpoints (9.0.0) diff --git a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts index 3ef9d698a9fb4..86c40e423db37 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts @@ -517,6 +517,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D aiAssistant: `${SECURITY_SOLUTION_DOCS}security-assistant.html`, signalsMigrationApi: `${SECURITY_SOLUTION_DOCS}signals-migration-api.html`, legacyEndpointManagementApiDeprecations: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-199598`, + legacyBulkApiDeprecations: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-207091`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, diff --git a/src/platform/packages/shared/kbn-doc-links/src/types.ts b/src/platform/packages/shared/kbn-doc-links/src/types.ts index 4d47acde7fe3b..b22bdee57373d 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/types.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/types.ts @@ -379,6 +379,7 @@ export interface DocLinks { readonly detectionEngineOverview: string; readonly signalsMigrationApi: string; readonly legacyEndpointManagementApiDeprecations: string; + readonly legacyBulkApiDeprecations: string; }; readonly query: { readonly eql: string; diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index 8686d0b442e35..777d2db10b70a 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -246,6 +246,7 @@ export const DETECTION_ENGINE_RULES_BULK_CREATE = `${DETECTION_ENGINE_RULES_URL}/_bulk_create` as const; export const DETECTION_ENGINE_RULES_BULK_UPDATE = `${DETECTION_ENGINE_RULES_URL}/_bulk_update` as const; +export const DETECTION_ENGINE_RULES_IMPORT_URL = `${DETECTION_ENGINE_RULES_URL}/_import` as const; export * from './entity_analytics/constants'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 0ccf26d822d56..6dabaa9a89ff4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -40,6 +40,7 @@ import { import type { BulkActionsDryRunErrCode } from '../../../../common/constants'; import { DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_IMPORT_URL, DETECTION_ENGINE_RULES_PREVIEW, DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_RULES_URL_FIND, @@ -455,21 +456,18 @@ export const importRules = async ({ const formData = new FormData(); formData.append('file', fileToImport); - return KibanaServices.get().http.fetch( - `${DETECTION_ENGINE_RULES_URL}/_import`, - { - method: 'POST', - version: '2023-10-31', - headers: { 'Content-Type': undefined }, - query: { - overwrite, - overwrite_exceptions: overwriteExceptions, - overwrite_action_connectors: overwriteActionConnectors, - }, - body: formData, - signal, - } - ); + return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_IMPORT_URL, { + method: 'POST', + version: '2023-10-31', + headers: { 'Content-Type': undefined }, + query: { + overwrite, + overwrite_exceptions: overwriteExceptions, + overwrite_action_connectors: overwriteActionConnectors, + }, + body: formData, + signal, + }); }; /** diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 3a48bb449c55d..4ed9b44976f2f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -29,6 +29,7 @@ import { DETECTION_ENGINE_RULES_BULK_DELETE, DETECTION_ENGINE_RULES_BULK_CREATE, DETECTION_ENGINE_RULES_URL_FIND, + DETECTION_ENGINE_RULES_IMPORT_URL, } from '../../../../../common/constants'; import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../common/api/detection_engine/rule_management/urls'; @@ -260,14 +261,14 @@ export const getFindResultWithMultiHits = ({ export const getImportRulesRequest = (hapiStream?: HapiReadableStream) => requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_import`, + path: DETECTION_ENGINE_RULES_IMPORT_URL, body: { file: hapiStream }, }); export const getImportRulesRequestOverwriteTrue = (hapiStream?: HapiReadableStream) => requestMock.create({ method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_import`, + path: DETECTION_ENGINE_RULES_IMPORT_URL, body: { file: hapiStream }, query: { overwrite: true }, }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts index e6999cc9e429e..3947854701d18 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts @@ -5,16 +5,16 @@ * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { DocLinksServiceSetup, Logger } from '@kbn/core/server'; import type { ConfigType } from '../../../../config'; import type { SetupPlugins } from '../../../../plugin_contract'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { performBulkActionRoute } from './rules/bulk_actions/route'; import { bulkCreateRulesRoute } from './rules/bulk_create_rules/route'; -import { bulkDeleteRulesRoute } from './rules/bulk_delete_rules/route'; -import { bulkPatchRulesRoute } from './rules/bulk_patch_rules/route'; import { bulkUpdateRulesRoute } from './rules/bulk_update_rules/route'; +import { bulkPatchRulesRoute } from './rules/bulk_patch_rules/route'; +import { bulkDeleteRulesRoute } from './rules/bulk_delete_rules/route'; import { createRuleRoute } from './rules/create_rule/route'; import { deleteRuleRoute } from './rules/delete_rule/route'; import { exportRulesRoute } from './rules/export_rules/route'; @@ -31,7 +31,8 @@ export const registerRuleManagementRoutes = ( router: SecuritySolutionPluginRouter, config: ConfigType, ml: SetupPlugins['ml'], - logger: Logger + logger: Logger, + docLinks: DocLinksServiceSetup ) => { // Rules CRUD createRuleRoute(router); @@ -40,11 +41,11 @@ export const registerRuleManagementRoutes = ( patchRuleRoute(router); deleteRuleRoute(router); - // Rules bulk CRUD - bulkCreateRulesRoute(router, logger); - bulkUpdateRulesRoute(router, logger); - bulkPatchRulesRoute(router, logger); - bulkDeleteRulesRoute(router, logger); + // These four bulk endpoints are deprecated and will be removed in 9.0 + bulkCreateRulesRoute(router, logger, docLinks); + bulkUpdateRulesRoute(router, logger, docLinks); + bulkPatchRulesRoute(router, logger, docLinks); + bulkDeleteRulesRoute(router, logger, docLinks); // Rules bulk actions performBulkActionRoute(router, config, ml, logger); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts index d6403f0d1553d..5e1ddef6263c0 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts @@ -20,7 +20,7 @@ import { bulkCreateRulesRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock, docLinksServiceMock } from '@kbn/core/server/mocks'; import { HttpAuthzError } from '../../../../../machine_learning/validation'; import { getRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/rule_response_schema.mock'; @@ -32,6 +32,7 @@ describe('Bulk create rules route', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); const logger = loggingSystemMock.createLogger(); + const docLinks = docLinksServiceMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules clients.rulesClient.create.mockResolvedValue(getRuleMock(getQueryRuleParams())); // successful creation @@ -39,7 +40,7 @@ describe('Bulk create rules route', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - bulkCreateRulesRoute(server.router, logger); + bulkCreateRulesRoute(server.router, logger, docLinks); }); describe('status codes', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index c868ddf7ffe22..962ccced11de8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -5,11 +5,14 @@ * 2.0. */ -import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import type { DocLinksServiceSetup, IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_CREATE, + DETECTION_ENGINE_RULES_IMPORT_URL, +} from '../../../../../../../common/constants'; import { BulkCreateRulesRequestBody, validateCreateRuleProps, @@ -32,7 +35,11 @@ import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../. /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const bulkCreateRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkCreateRulesRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + docLinks: DocLinksServiceSetup +) => { router.versioned .post({ access: 'public', @@ -56,6 +63,17 @@ export const bulkCreateRulesRoute = (router: SecuritySolutionPluginRouter, logge body: buildRouteValidationWithZod(BulkCreateRulesRequestBody), }, }, + options: { + deprecated: { + documentationUrl: docLinks.links.securitySolution.legacyBulkApiDeprecations, + severity: 'critical', + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: DETECTION_ENGINE_RULES_IMPORT_URL, + }, + }, + }, }, async (context, request, response): Promise> => { logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_CREATE); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts index 750419c464b2a..421e71ecde7ce 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts @@ -17,7 +17,7 @@ import { } from '../../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { bulkDeleteRulesRoute } from './route'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock, docLinksServiceMock } from '@kbn/core/server/mocks'; describe('Bulk delete rules route', () => { let server: ReturnType; @@ -27,12 +27,13 @@ describe('Bulk delete rules route', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); const logger = loggingSystemMock.createLogger(); + const docLinks = docLinksServiceMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists clients.rulesClient.delete.mockResolvedValue({}); // successful deletion clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request - bulkDeleteRulesRoute(server.router, logger); + bulkDeleteRulesRoute(server.router, logger, docLinks); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 1a73b0802188c..f495ed9763bfa 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -6,7 +6,12 @@ */ import type { VersionedRouteConfig } from '@kbn/core-http-server'; -import type { IKibanaResponse, Logger, RequestHandler } from '@kbn/core/server'; +import type { + DocLinksServiceSetup, + IKibanaResponse, + Logger, + RequestHandler, +} from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { @@ -19,7 +24,10 @@ import { BulkDeleteRulesRequestBody, validateQueryRuleByIds, } from '../../../../../../../common/api/detection_engine/rule_management'; -import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_BULK_DELETE, +} from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, @@ -46,7 +54,11 @@ type Handler = RequestHandler< /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkDeleteRulesRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + docLinks: DocLinksServiceSetup +) => { const handler: Handler = async ( context, request, @@ -124,6 +136,17 @@ export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logge body: buildRouteValidationWithZod(BulkDeleteRulesRequestBody), }, }, + options: { + deprecated: { + documentationUrl: docLinks.links.securitySolution.legacyBulkApiDeprecations, + severity: 'critical', + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: DETECTION_ENGINE_RULES_BULK_ACTION, + }, + }, + }, }, handler ); @@ -135,6 +158,17 @@ export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logge body: buildRouteValidationWithZod(BulkDeleteRulesPostRequestBody), }, }, + options: { + deprecated: { + documentationUrl: docLinks.links.securitySolution.legacyBulkApiDeprecations, + severity: 'critical', + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: DETECTION_ENGINE_RULES_BULK_ACTION, + }, + }, + }, }, handler ); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index c6a2ef1b83d8c..8327054197863 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -24,7 +24,7 @@ import { import { bulkPatchRulesRoute } from './route'; import { getCreateRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks'; import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock, docLinksServiceMock } from '@kbn/core/server/mocks'; import { HttpAuthzError } from '../../../../../machine_learning/validation'; describe('Bulk patch rules route', () => { @@ -35,12 +35,13 @@ describe('Bulk patch rules route', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); const logger = loggingSystemMock.createLogger(); + const docLinks = docLinksServiceMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists clients.rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); // update succeeds clients.detectionRulesClient.patchRule.mockResolvedValue(getRulesSchemaMock()); - bulkPatchRulesRoute(server.router, logger); + bulkPatchRulesRoute(server.router, logger, docLinks); }); describe('status codes', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index a47aab03efc1d..89ddd3afcd1ec 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -5,11 +5,14 @@ * 2.0. */ -import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import type { DocLinksServiceSetup, IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_BULK_UPDATE, +} from '../../../../../../../common/constants'; import { BulkPatchRulesRequestBody, BulkCrudRulesResponse, @@ -26,7 +29,11 @@ import { RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS } from '../../timeouts'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const bulkPatchRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkPatchRulesRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + docLinks: DocLinksServiceSetup +) => { router.versioned .patch({ access: 'public', @@ -50,6 +57,17 @@ export const bulkPatchRulesRoute = (router: SecuritySolutionPluginRouter, logger body: buildRouteValidationWithZod(BulkPatchRulesRequestBody), }, }, + options: { + deprecated: { + documentationUrl: docLinks.links.securitySolution.legacyBulkApiDeprecations, + severity: 'critical', + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: DETECTION_ENGINE_RULES_BULK_ACTION, + }, + }, + }, }, async (context, request, response): Promise> => { logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_UPDATE); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts index ebdc1604346b5..53716e1774c77 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts @@ -19,7 +19,7 @@ import { bulkUpdateRulesRoute } from './route'; import type { BulkError } from '../../../../routes/utils'; import { getCreateRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { loggingSystemMock, docLinksServiceMock } from '@kbn/core/server/mocks'; import { HttpAuthzError } from '../../../../../machine_learning/validation'; describe('Bulk update rules route', () => { @@ -30,13 +30,14 @@ describe('Bulk update rules route', () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); const logger = loggingSystemMock.createLogger(); + const docLinks = docLinksServiceMock.createSetupContract(); clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); clients.rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); clients.detectionRulesClient.updateRule.mockResolvedValue(getRulesSchemaMock()); clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index'); - bulkUpdateRulesRoute(server.router, logger); + bulkUpdateRulesRoute(server.router, logger, docLinks); }); describe('status codes', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index 52185633d1007..a7d8047d0bf35 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import type { DocLinksServiceSetup, IKibanaResponse, Logger } from '@kbn/core/server'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import { @@ -14,7 +14,10 @@ import { BulkCrudRulesResponse, } from '../../../../../../../common/api/detection_engine/rule_management'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_BULK_UPDATE, +} from '../../../../../../../common/constants'; import { getIdBulkError } from '../../../utils/utils'; import { transformBulkError, @@ -30,7 +33,11 @@ import { RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS } from '../../timeouts'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const bulkUpdateRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkUpdateRulesRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + docLinks: DocLinksServiceSetup +) => { router.versioned .put({ access: 'public', @@ -54,6 +61,17 @@ export const bulkUpdateRulesRoute = (router: SecuritySolutionPluginRouter, logge body: buildRouteValidationWithZod(BulkUpdateRulesRequestBody), }, }, + options: { + deprecated: { + documentationUrl: docLinks.links.securitySolution.legacyBulkApiDeprecations, + severity: 'critical', + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: DETECTION_ENGINE_RULES_BULK_ACTION, + }, + }, + }, }, async (context, request, response): Promise> => { logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_UPDATE); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index d6a5213fcbea6..0f586e4cbc14a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -15,7 +15,7 @@ import { ImportRulesRequestQuery, ImportRulesResponse, } from '../../../../../../../common/api/detection_engine/rule_management'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '../../../../../../../common/constants'; import type { ConfigType } from '../../../../../../config'; import type { HapiReadableStream, SecuritySolutionPluginRouter } from '../../../../../../types'; import type { ImportRuleResponse } from '../../../../routes/utils'; @@ -46,7 +46,7 @@ export const importRulesRoute = (router: SecuritySolutionPluginRouter, config: C router.versioned .post({ access: 'public', - path: `${DETECTION_ENGINE_RULES_URL}/_import`, + path: DETECTION_ENGINE_RULES_IMPORT_URL, security: { authz: { requiredPrivileges: ['securitySolution'], diff --git a/x-pack/solutions/security/plugins/security_solution/server/routes/index.ts b/x-pack/solutions/security/plugins/security_solution/server/routes/index.ts index ca1cbb493311f..0e6a8a056ac86 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/routes/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/routes/index.ts @@ -89,7 +89,7 @@ export const initRoutes = ( registerPrebuiltRulesRoutes(router, config); registerRuleExceptionsRoutes(router); registerManageExceptionsRoutes(router); - registerRuleManagementRoutes(router, config, ml, logger); + registerRuleManagementRoutes(router, config, ml, logger, docLinks); registerRuleMonitoringRoutes(router); registerRulePreviewRoutes( router, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts index e7c2f8273fb91..898cacfb51927 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts @@ -213,7 +213,7 @@ export default ({ getService }: FtrProviderContext): void => { // it('should be able to import 10000 rules', async () => { // const ruleIds = new Array(10000).fill(undefined).map((_, index) => `rule-${index}`); // const { body } = await supertest - // .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + // .post(DETECTION_ENGINE_RULES_IMPORT_URL) // .set('kbn-xsrf', 'true') // .attach('file', getSimpleRuleAsNdjson(ruleIds, false), 'rules.ndjson') // .expect(200); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules_with_overwrite.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules_with_overwrite.ts index c58f20a84db8f..a43c25b35c426 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules_with_overwrite.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules_with_overwrite.ts @@ -7,7 +7,7 @@ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { combineToNdJson, getCustomQueryRuleParams, fetchRule } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -55,14 +55,14 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') .expect(200); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -94,7 +94,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -150,7 +150,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts index 5edaabf86c093..29d5cca51e525 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_connectors.ts @@ -6,7 +6,7 @@ */ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; import { deleteAllRules } from '../../../../../../common/utils/security_solution'; import { combineToNdJson, getCustomQueryRuleParams } from '../../../utils'; import { createConnector, deleteConnector, getConnector } from '../../../utils/connectors'; @@ -79,7 +79,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -106,7 +106,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(CUSTOM_ACTION_CONNECTOR); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -154,7 +154,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -200,7 +200,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -262,7 +262,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -308,7 +308,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -389,7 +389,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite_action_connectors=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -435,7 +435,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite_action_connectors=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -494,7 +494,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite_action_connectors=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite_action_connectors=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts index 3ab680b38d835..b783db0d775b6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_export_rules.ts @@ -16,7 +16,10 @@ import { getCreateExceptionListDetectionSchemaMock, getCreateExceptionListMinimalSchemaMock, } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + DETECTION_ENGINE_RULES_IMPORT_URL, + DETECTION_ENGINE_RULES_URL, +} from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { binaryToString, getCustomQueryRuleParams } from '../../../utils'; import { @@ -121,7 +124,7 @@ export default ({ getService }: FtrProviderContext): void => { .parse(binaryToString); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true&overwrite_exceptions=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(body), 'rules.ndjson') @@ -203,7 +206,7 @@ export default ({ getService }: FtrProviderContext): void => { .parse(binaryToString); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true&overwrite_exceptions=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(body), 'rules.ndjson') @@ -294,7 +297,7 @@ export default ({ getService }: FtrProviderContext): void => { .parse(binaryToString); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true&overwrite_exceptions=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(body), 'rules.ndjson') @@ -376,7 +379,7 @@ export default ({ getService }: FtrProviderContext): void => { .parse(binaryToString); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true&overwrite_exceptions=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(body), 'rules.ndjson') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts index 2dc5358f0f7ad..112c14a3c7929 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules.ts @@ -10,7 +10,7 @@ import { range } from 'lodash'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; import { getImportExceptionsListItemSchemaMock, getImportExceptionsListSchemaMock, @@ -184,7 +184,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -208,7 +208,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -235,7 +235,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -267,7 +267,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -331,7 +331,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -353,7 +353,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(getCustomQueryRuleParams()); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -363,7 +363,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should reject with an error if the file type is not that of a ndjson', async () => { const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(''), 'rules.txt') @@ -379,7 +379,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(getCustomQueryRuleParams()); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -398,7 +398,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(ruleToImport); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -416,7 +416,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -437,7 +437,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -467,7 +467,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(existingRule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -511,7 +511,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -562,7 +562,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -608,7 +608,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(existingRule1, existingRule2, ruleToImportSuccessfully); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -638,7 +638,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -706,7 +706,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -748,7 +748,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -823,7 +823,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -885,7 +885,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -946,7 +946,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleBuffer(space714ActionConnectorId); const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_IMPORT_URL}`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -965,7 +965,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_IMPORT_URL}`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -989,7 +989,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleWithConnectorsBuffer(differentSpaceConnectorId); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1013,7 +1013,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleBuffer(space714ActionConnectorId); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1040,7 +1040,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleBuffer(space714ActionConnectorId); const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_IMPORT_URL}`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1074,7 +1074,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleWithConnectorsBuffer(defaultSpaceConnectorId); const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_IMPORT_URL}`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1100,7 +1100,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1124,7 +1124,7 @@ export default ({ getService }: FtrProviderContext): void => { const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) + .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_IMPORT_URL}`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', buffer, 'rules.ndjson') @@ -1166,7 +1166,7 @@ export default ({ getService }: FtrProviderContext): void => { // import old exception version const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1198,7 +1198,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1250,7 +1250,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1301,7 +1301,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1393,7 +1393,7 @@ export default ({ getService }: FtrProviderContext): void => { // Importing the "simpleRule", along with the exception list // it's referencing and the list's item const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1468,7 +1468,7 @@ export default ({ getService }: FtrProviderContext): void => { const importPayload = combineArraysToNdJson(rules, exceptionLists, exceptionItems); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(importPayload), 'rules.ndjson') @@ -1549,7 +1549,7 @@ export default ({ getService }: FtrProviderContext): void => { // Importing the "simpleRule", along with the exception list // it's referencing and the list's item const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1615,7 +1615,7 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .attach('file', Buffer.from(ndjson), 'rules.ndjson') .expect('Content-Type', 'application/json; charset=utf-8') @@ -1634,7 +1634,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1667,7 +1667,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1700,7 +1700,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -1722,7 +1722,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(rule); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts index c78af6078133e..40123a99f3d69 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_ess.ts @@ -7,7 +7,7 @@ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { createLegacyRuleAction, @@ -67,7 +67,7 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -93,7 +93,7 @@ export default ({ getService }: FtrProviderContext): void => { const ndjson = combineToNdJson(getCustomQueryRuleParams()); const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .auth(ROLES.hunter_no_actions, 'changeme') .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') @@ -146,7 +146,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .auth(ROLES.hunter, 'changeme') .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') @@ -217,7 +217,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .auth(ROLES.hunter_no_actions, 'changeme') .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') @@ -269,7 +269,7 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -308,7 +308,7 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -345,7 +345,7 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .post(DETECTION_ENGINE_RULES_IMPORT_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_with_overwrite.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_with_overwrite.ts index c58f20a84db8f..a43c25b35c426 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_with_overwrite.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/import_rules_with_overwrite.ts @@ -7,7 +7,7 @@ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { DETECTION_ENGINE_RULES_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants'; import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { combineToNdJson, getCustomQueryRuleParams, fetchRule } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -55,14 +55,14 @@ export default ({ getService }: FtrProviderContext): void => { ); await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') .expect(200); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -94,7 +94,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') @@ -150,7 +150,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .post(`${DETECTION_ENGINE_RULES_IMPORT_URL}?overwrite=true`) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .attach('file', Buffer.from(ndjson), 'rules.ndjson') From d13cefd7c776ca97949f474f26365013ac931c89 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 22 Jan 2025 20:24:07 +0000 Subject: [PATCH 07/12] skip flaky suite (#205953) --- .../user_actions/delete_attachment_confirmation_modal.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx b/x-pack/platform/plugins/shared/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx index fb58ed73b7ba2..7a9eb349b233a 100644 --- a/x-pack/platform/plugins/shared/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx +++ b/x-pack/platform/plugins/shared/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx @@ -10,7 +10,8 @@ import React from 'react'; import { DeleteAttachmentConfirmationModal } from './delete_attachment_confirmation_modal'; import { render, screen } from '@testing-library/react'; -describe('DeleteAttachmentConfirmationModal', () => { +// FLAKY: https://github.com/elastic/kibana/issues/205953 +describe.skip('DeleteAttachmentConfirmationModal', () => { const props = { title: 'My title', confirmButtonText: 'My button text', From 14b57223c4e0fe7fe0f03acfad5fbc2a51c61e24 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 22 Jan 2025 20:26:16 +0000 Subject: [PATCH 08/12] skip flaky suite (#204381) --- test/functional/apps/console/_console.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index c377bd8907c99..1f9f94ca51b62 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -90,7 +90,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('tabs navigation', () => { + // FLAKY: https://github.com/elastic/kibana/issues/204381 + describe.skip('tabs navigation', () => { let currentUrl: string; beforeEach(async () => { From ae8be0d4857f4d30d7f022ceb4a79950cbf732c2 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:00:58 +1100 Subject: [PATCH 09/12] [8.x] [Response Ops] [Rule Form] Add Show Request and Add Action screens to flyout (#206154) (#207903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[Response Ops] [Rule Form] Add Show Request and Add Action screens to flyout (#206154)](https://github.com/elastic/kibana/pull/206154) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Zacqary Adam Xeper --- .../response-ops/rule_form/src/hooks/index.ts | 1 + .../src/hooks/use_rule_form_screen_context.ts | 15 + .../src/hooks/use_rule_form_steps.tsx | 30 +- .../rule_form/src/request_code_block/index.ts | 10 + .../request_code_block/request_code_block.tsx | 67 +++ .../src/rule_actions/rule_actions.test.tsx | 32 +- .../src/rule_actions/rule_actions.tsx | 69 +-- .../rule_actions_connectors_body.test.tsx | 100 ++++ .../rule_actions_connectors_body.tsx | 467 ++++++++++++++++++ .../rule_actions_connectors_modal.test.tsx | 94 +--- .../rule_actions_connectors_modal.tsx | 340 +------------ .../src/rule_flyout/rule_flyout.test.tsx | 24 +- .../rule_form/src/rule_flyout/rule_flyout.tsx | 59 ++- .../src/rule_flyout/rule_flyout_body.tsx | 11 +- .../rule_flyout_select_connector.tsx | 65 +++ .../rule_flyout/rule_flyout_show_request.tsx | 73 +++ .../response-ops/rule_form/src/rule_form.scss | 30 +- .../response-ops/rule_form/src/rule_form.tsx | 5 +- .../src/rule_form_screen_context/index.ts | 10 + .../rule_form_screen_context.tsx | 41 ++ .../rule_form/src/rule_page/rule_page.tsx | 8 +- .../src/rule_page/rule_page_footer.test.tsx | 10 +- .../src/rule_page/rule_page_footer.tsx | 17 +- .../rule_page_show_request_modal.test.tsx | 16 +- .../rule_page_show_request_modal.tsx | 114 +---- .../rule_form/src/translations.ts | 63 +++ 26 files changed, 1124 insertions(+), 647 deletions(-) create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_screen_context.ts create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/request_code_block/index.ts create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/request_code_block/request_code_block.tsx create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.test.tsx create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.tsx create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_select_connector.tsx create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_show_request.tsx create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/index.ts create mode 100644 src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/rule_form_screen_context.tsx diff --git a/src/platform/packages/shared/response-ops/rule_form/src/hooks/index.ts b/src/platform/packages/shared/response-ops/rule_form/src/hooks/index.ts index aef31dc3d5135..d10d3e3328883 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/hooks/index.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/hooks/index.ts @@ -10,3 +10,4 @@ export * from './use_rule_form_dispatch'; export * from './use_rule_form_state'; export * from './use_rule_form_steps'; +export * from './use_rule_form_screen_context'; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_screen_context.ts b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_screen_context.ts new file mode 100644 index 0000000000000..806d65c1f8241 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_screen_context.ts @@ -0,0 +1,15 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { useContext } from 'react'; +import { RuleFormScreenContext } from '../rule_form_screen_context'; + +export const useRuleFormScreenContext = () => { + return useContext(RuleFormScreenContext); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx index 74b926a2bc20c..80dc1ff05ae1f 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/hooks/use_rule_form_steps.tsx @@ -149,13 +149,7 @@ const useCommonRuleFormSteps = ({ ? { title: RULE_FORM_PAGE_RULE_ACTIONS_TITLE, status: actionsStatus, - children: ( - <> - - - - - ), + children: , } : null, [RuleFormStepId.DETAILS]: { @@ -163,13 +157,7 @@ const useCommonRuleFormSteps = ({ ? RULE_FORM_PAGE_RULE_DETAILS_TITLE_SHORT : RULE_FORM_PAGE_RULE_DETAILS_TITLE, status: ruleDetailsStatus, - children: ( - <> - - - - - ), + children: , }, }), [ruleDefinitionStatus, canReadConnectors, actionsStatus, ruleDetailsStatus, shortTitles] @@ -210,7 +198,7 @@ export const useRuleFormSteps: () => RuleFormVerticalSteps = () => { const mappedSteps = useMemo(() => { return stepOrder - .map((stepId) => { + .map((stepId, index) => { const step = steps[stepId]; return step ? { @@ -227,6 +215,12 @@ export const useRuleFormSteps: () => RuleFormVerticalSteps = () => { stepId={stepId} > {step.children} + {index > 0 && ( + <> + + + + )} ), } @@ -246,8 +240,10 @@ interface RuleFormHorizontalSteps { hasNextStep: boolean; hasPreviousStep: boolean; } -export const useRuleFormHorizontalSteps: () => RuleFormHorizontalSteps = () => { - const [currentStep, setCurrentStep] = useState(STEP_ORDER[0]); +export const useRuleFormHorizontalSteps: ( + initialStep?: RuleFormStepId +) => RuleFormHorizontalSteps = (initialStep = STEP_ORDER[0]) => { + const [currentStep, setCurrentStep] = useState(initialStep); const [touchedSteps, setTouchedSteps] = useState>( STEP_ORDER.reduce( (result, stepId) => ({ ...result, [stepId]: false }), diff --git a/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/index.ts b/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/index.ts new file mode 100644 index 0000000000000..18fa50fae60cb --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export * from './request_code_block'; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/request_code_block.tsx b/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/request_code_block.tsx new file mode 100644 index 0000000000000..ff229564cc281 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/request_code_block/request_code_block.tsx @@ -0,0 +1,67 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { omit, pick } from 'lodash'; +import React, { useMemo } from 'react'; +import { EuiCodeBlock } from '@elastic/eui'; +import { + CreateRuleBody, + UPDATE_FIELDS_WITH_ACTIONS, + UpdateRuleBody, + transformCreateRuleBody, + transformUpdateRuleBody, +} from '../common/apis'; +import { BASE_ALERTING_API_PATH } from '../constants'; +import { useRuleFormState } from '../hooks'; +import { SHOW_REQUEST_MODAL_ERROR } from '../translations'; +import { RuleFormData } from '../types'; + +const stringifyBodyRequest = ({ + formData, + isEdit, +}: { + formData: RuleFormData; + isEdit: boolean; +}): string => { + try { + const request = isEdit + ? transformUpdateRuleBody(pick(formData, UPDATE_FIELDS_WITH_ACTIONS) as UpdateRuleBody) + : transformCreateRuleBody(omit(formData, 'id') as CreateRuleBody); + return JSON.stringify(request, null, 2); + } catch { + return SHOW_REQUEST_MODAL_ERROR; + } +}; + +interface RequestCodeBlockProps { + isEdit: boolean; + 'data-test-subj'?: string; +} +export const RequestCodeBlock = (props: RequestCodeBlockProps) => { + const { isEdit, 'data-test-subj': dataTestSubj } = props; + const { formData, id, multiConsumerSelection } = useRuleFormState(); + + const formattedRequest = useMemo(() => { + return stringifyBodyRequest({ + formData: { + ...formData, + ...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}), + }, + isEdit, + }); + }, [formData, isEdit, multiConsumerSelection]); + + return ( + + {`${isEdit ? 'PUT' : 'POST'} kbn:${BASE_ALERTING_API_PATH}/rule${ + isEdit ? `/${id}` : '' + }\n${formattedRequest}`} + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.test.tsx index e172405e3695b..de7e92ef7d756 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.test.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.test.tsx @@ -28,6 +28,7 @@ const http = httpServiceMock.createStartContract(); jest.mock('../hooks', () => ({ useRuleFormState: jest.fn(), useRuleFormDispatch: jest.fn(), + useRuleFormScreenContext: jest.fn(), })); jest.mock('./rule_actions_system_actions_item', () => ({ @@ -94,7 +95,8 @@ const mockValidate = jest.fn().mockResolvedValue({ errors: {}, }); -const { useRuleFormState, useRuleFormDispatch } = jest.requireMock('../hooks'); +const { useRuleFormState, useRuleFormDispatch, useRuleFormScreenContext } = + jest.requireMock('../hooks'); const { useLoadConnectors, useLoadConnectorTypes, useLoadRuleTypeAadTemplateField } = jest.requireMock('../common/hooks'); @@ -109,6 +111,7 @@ const mockActions = [getAction('1'), getAction('2')]; const mockSystemActions = [getSystemAction('3')]; const mockOnChange = jest.fn(); +const mockSetIsConnectorsScreenVisible = jest.fn(); describe('ruleActions', () => { beforeEach(() => { @@ -167,6 +170,9 @@ describe('ruleActions', () => { aadTemplateFields: [], }); useRuleFormDispatch.mockReturnValue(mockOnChange); + useRuleFormScreenContext.mockReturnValue({ + setIsConnectorsScreenVisible: mockSetIsConnectorsScreenVisible, + }); }); afterEach(() => { @@ -216,29 +222,7 @@ describe('ruleActions', () => { render(); await userEvent.click(screen.getByTestId('ruleActionsAddActionButton')); - expect(screen.getByText('RuleActionsConnectorsModal')).toBeInTheDocument(); - }); - - test('should call onSelectConnector with the correct parameters', async () => { - render(); - - await userEvent.click(screen.getByTestId('ruleActionsAddActionButton')); - expect(screen.getByText('RuleActionsConnectorsModal')).toBeInTheDocument(); - - await userEvent.click(screen.getByText('select connector')); - expect(mockOnChange).toHaveBeenCalledWith({ - payload: { - actionTypeId: 'actionType-1', - frequency: { notifyWhen: 'onActionGroupChange', summary: false, throttle: null }, - group: 'test', - id: 'connector-1', - params: { key: 'value' }, - uuid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - }, - type: 'addAction', - }); - - expect(screen.queryByText('RuleActionsConnectorsModal')).not.toBeInTheDocument(); + expect(mockSetIsConnectorsScreenVisible).toHaveBeenCalledWith(true); }); test('should use the rule producer ID if it is not a multi-consumer rule', async () => { diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx index 168a7d4f5a3c3..e3f9e76410c87 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions.tsx @@ -9,21 +9,17 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiImage, EuiSpacer, EuiText } from '@elastic/eui'; import { RuleSystemAction } from '@kbn/alerting-types'; -import { ActionConnector } from '@kbn/alerts-ui-shared'; import React, { useCallback, useMemo, useState } from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { v4 as uuidv4 } from 'uuid'; -import { RuleAction, RuleFormParamsErrors } from '../common/types'; -import { DEFAULT_FREQUENCY, MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; -import { useRuleFormDispatch, useRuleFormState } from '../hooks'; +import { RuleAction } from '../common/types'; +import { MULTI_CONSUMER_RULE_TYPE_IDS } from '../constants'; +import { useRuleFormState, useRuleFormScreenContext } from '../hooks'; import { ADD_ACTION_DESCRIPTION_TEXT, ADD_ACTION_HEADER, ADD_ACTION_OPTIONAL_TEXT, ADD_ACTION_TEXT, } from '../translations'; -import { getDefaultParams } from '../utils'; -import { RuleActionsConnectorsModal } from './rule_actions_connectors_modal'; import { RuleActionsItem } from './rule_actions_item'; import { RuleActionsSystemActionsItem } from './rule_actions_system_actions_item'; @@ -40,69 +36,19 @@ const useRuleActionsIllustration = () => { }; export const RuleActions = () => { - const [isConnectorModalOpen, setIsConnectorModalOpen] = useState(false); const ruleActionsIllustration = useRuleActionsIllustration(); + const { setIsConnectorsScreenVisible } = useRuleFormScreenContext(); const { formData: { actions, consumer }, - plugins: { actionTypeRegistry }, multiConsumerSelection, selectedRuleType, connectorTypes, } = useRuleFormState(); - const dispatch = useRuleFormDispatch(); - const onModalOpen = useCallback(() => { - setIsConnectorModalOpen(true); - }, []); - - const onModalClose = useCallback(() => { - setIsConnectorModalOpen(false); - }, []); - - const onSelectConnector = useCallback( - async (connector: ActionConnector) => { - const { id, actionTypeId } = connector; - const uuid = uuidv4(); - const group = selectedRuleType.defaultActionGroupId; - const actionTypeModel = actionTypeRegistry.get(actionTypeId); - - const params = - getDefaultParams({ - group, - ruleType: selectedRuleType, - actionTypeModel, - }) || {}; - - dispatch({ - type: 'addAction', - payload: { - id, - actionTypeId, - uuid, - params, - group, - frequency: DEFAULT_FREQUENCY, - }, - }); - - const res: { errors: RuleFormParamsErrors } = await actionTypeRegistry - .get(actionTypeId) - ?.validateParams(params); - - dispatch({ - type: 'setActionParamsError', - payload: { - uuid, - errors: res.errors, - }, - }); - - onModalClose(); - }, - [dispatch, onModalClose, selectedRuleType, actionTypeRegistry] - ); + setIsConnectorsScreenVisible(true); + }, [setIsConnectorsScreenVisible]); const producerId = useMemo(() => { if (MULTI_CONSUMER_RULE_TYPE_IDS.includes(selectedRuleType.id)) { @@ -184,9 +130,6 @@ export const RuleActions = () => { - {isConnectorModalOpen && ( - - )} ); }; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.test.tsx new file mode 100644 index 0000000000000..0ac987016ff0e --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.test.tsx @@ -0,0 +1,100 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { RuleActionsConnectorsBody } from './rule_actions_connectors_body'; +import type { ActionConnector, ActionTypeModel } from '@kbn/alerts-ui-shared'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/lib'; +import { ActionType } from '@kbn/actions-types'; +import { + getActionType, + getActionTypeModel, + getConnector, +} from '../common/test_utils/actions_test_utils'; + +jest.mock('../hooks', () => ({ + useRuleFormState: jest.fn(), + useRuleFormDispatch: jest.fn(), +})); + +jest.mock('../utils', () => ({ + getDefaultParams: jest.fn(), +})); + +const { useRuleFormState, useRuleFormDispatch } = jest.requireMock('../hooks'); + +const mockConnectors: ActionConnector[] = [getConnector('1'), getConnector('2')]; + +const mockActionTypes: ActionType[] = [getActionType('1'), getActionType('2')]; + +const mockOnSelectConnector = jest.fn(); + +const mockOnChange = jest.fn(); + +describe('ruleActionsConnectorsBody', () => { + beforeEach(() => { + const actionTypeRegistry = new TypeRegistry(); + actionTypeRegistry.register(getActionTypeModel('1', { id: 'actionType-1' })); + actionTypeRegistry.register(getActionTypeModel('2', { id: 'actionType-2' })); + + useRuleFormState.mockReturnValue({ + plugins: { + actionTypeRegistry, + }, + formData: { + actions: [], + }, + connectors: mockConnectors, + connectorTypes: mockActionTypes, + aadTemplateFields: [], + selectedRuleType: { + defaultActionGroupId: 'default', + }, + }); + useRuleFormDispatch.mockReturnValue(mockOnChange); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call onSelectConnector when connector is clicked', async () => { + render(); + + await userEvent.click(screen.getByText('connector-1')); + await waitFor(() => + expect(mockOnSelectConnector).toHaveBeenLastCalledWith({ + actionTypeId: 'actionType-1', + config: { config: 'config-1' }, + id: 'connector-1', + isDeprecated: false, + isPreconfigured: false, + isSystemAction: false, + name: 'connector-1', + secrets: { secret: 'secret' }, + }) + ); + + await userEvent.click(screen.getByText('connector-2')); + await waitFor(() => + expect(mockOnSelectConnector).toHaveBeenLastCalledWith({ + actionTypeId: 'actionType-2', + config: { config: 'config-2' }, + id: 'connector-2', + isDeprecated: false, + isPreconfigured: false, + isSystemAction: false, + name: 'connector-2', + secrets: { secret: 'secret' }, + }) + ); + }); +}); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.tsx new file mode 100644 index 0000000000000..a454856864fec --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_body.tsx @@ -0,0 +1,467 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiButton, + EuiCard, + EuiEmptyPrompt, + EuiFacetButton, + EuiFacetGroup, + EuiFieldSearch, + EuiFilterButton, + EuiFilterGroup, + EuiPopover, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiLoadingSpinner, + EuiSpacer, + EuiText, + EuiToolTip, + useEuiTheme, + EuiSelectable, + EuiSelectableProps, + useCurrentEuiBreakpoint, +} from '@elastic/eui'; +import { ActionConnector, checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared'; +import React, { Suspense, useCallback, useMemo, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { RuleFormParamsErrors } from '../common/types'; +import { DEFAULT_FREQUENCY } from '../constants'; +import { useRuleFormDispatch, useRuleFormState } from '../hooks'; +import { + ACTION_TYPE_MODAL_EMPTY_TEXT, + ACTION_TYPE_MODAL_EMPTY_TITLE, + ACTION_TYPE_MODAL_FILTER_ALL, + ACTION_TYPE_MODAL_FILTER_LIST_TITLE, + MODAL_SEARCH_CLEAR_FILTERS_TEXT, + MODAL_SEARCH_PLACEHOLDER, +} from '../translations'; +import { getDefaultParams } from '../utils'; + +type ConnectorsMap = Record; + +export interface RuleActionsConnectorsBodyProps { + onSelectConnector: (connector?: ActionConnector) => void; + responsiveOverflow?: 'auto' | 'hidden'; +} + +export const RuleActionsConnectorsBody = ({ + onSelectConnector, + responsiveOverflow = 'auto', +}: RuleActionsConnectorsBodyProps) => { + const [searchValue, setSearchValue] = useState(''); + const [selectedConnectorType, setSelectedConnectorType] = useState('all'); + const [isConenctorFilterPopoverOpen, setIsConenctorFilterPopoverOpen] = useState(false); + + const { euiTheme } = useEuiTheme(); + + const currentBreakpoint = useCurrentEuiBreakpoint() ?? 'm'; + + const { + plugins: { actionTypeRegistry }, + formData: { actions }, + connectors, + connectorTypes, + selectedRuleType, + } = useRuleFormState(); + + const dispatch = useRuleFormDispatch(); + + const onSelectConnectorInternal = useCallback( + async (connector: ActionConnector) => { + const { id, actionTypeId } = connector; + const uuid = uuidv4(); + const group = selectedRuleType.defaultActionGroupId; + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + + const params = + getDefaultParams({ + group, + ruleType: selectedRuleType, + actionTypeModel, + }) || {}; + + dispatch({ + type: 'addAction', + payload: { + id, + actionTypeId, + uuid, + params, + group, + frequency: DEFAULT_FREQUENCY, + }, + }); + + const res: { errors: RuleFormParamsErrors } = await actionTypeRegistry + .get(actionTypeId) + ?.validateParams(params); + + dispatch({ + type: 'setActionParamsError', + payload: { + uuid, + errors: res.errors, + }, + }); + + // Send connector to onSelectConnector mainly for testing purposes, dispatch handles form data updates + onSelectConnector(connector); + }, + [dispatch, onSelectConnector, selectedRuleType, actionTypeRegistry] + ); + + const preconfiguredConnectors = useMemo(() => { + return connectors.filter((connector) => connector.isPreconfigured); + }, [connectors]); + + const availableConnectors = useMemo(() => { + return connectors.filter(({ actionTypeId }) => { + const actionType = connectorTypes.find(({ id }) => id === actionTypeId); + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + + if (!actionType) { + return false; + } + + if (!actionTypeModel.actionParamsFields) { + return false; + } + + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionType, + preconfiguredConnectors + ); + + if (!actionType.enabledInConfig && !checkEnabledResult.isEnabled) { + return false; + } + + return true; + }); + }, [connectors, connectorTypes, preconfiguredConnectors, actionTypeRegistry]); + + const onSearchChange = useCallback((e: React.ChangeEvent) => { + setSearchValue(e.target.value); + }, []); + + const onConnectorOptionSelect = useCallback( + (id: string) => () => { + setSelectedConnectorType((prev) => { + if (prev === id) { + return 'all'; + } + return id; + }); + }, + [] + ); + + const onClearFilters = useCallback(() => { + setSearchValue(''); + setSelectedConnectorType('all'); + }, []); + + const connectorsMap: ConnectorsMap | null = useMemo(() => { + return availableConnectors.reduce((result, { actionTypeId }) => { + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + const subtype = actionTypeModel.subtype; + + const shownActionTypeId = actionTypeModel.hideInUi + ? subtype?.filter((type) => type.id !== actionTypeId)[0].id + : undefined; + + const currentActionTypeId = shownActionTypeId ? shownActionTypeId : actionTypeId; + + if (result[currentActionTypeId]) { + result[currentActionTypeId].total += 1; + } else { + result[currentActionTypeId] = { + actionTypeId: currentActionTypeId, + total: 1, + name: connectorTypes.find(({ id }) => id === currentActionTypeId)?.name || '', + }; + } + + return result; + }, {}); + }, [availableConnectors, connectorTypes, actionTypeRegistry]); + + const filteredConnectors = useMemo(() => { + return availableConnectors + .filter(({ actionTypeId }) => { + const subtype = actionTypeRegistry.get(actionTypeId).subtype?.map((type) => type.id); + + if (selectedConnectorType === 'all' || selectedConnectorType === '') { + return true; + } + + if (subtype?.includes(selectedConnectorType)) { + return subtype.includes(actionTypeId); + } + + return selectedConnectorType === actionTypeId; + }) + .filter(({ actionTypeId, name }) => { + const trimmedSearchValue = searchValue.trim().toLocaleLowerCase(); + if (trimmedSearchValue === '') { + return true; + } + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + const actionType = connectorTypes.find(({ id }) => id === actionTypeId); + const textSearchTargets = [ + name.toLocaleLowerCase(), + actionTypeModel.selectMessage?.toLocaleLowerCase(), + actionTypeModel.actionTypeTitle?.toLocaleLowerCase(), + actionType?.name?.toLocaleLowerCase(), + ]; + return textSearchTargets.some((text) => text?.includes(trimmedSearchValue)); + }); + }, [availableConnectors, selectedConnectorType, searchValue, connectorTypes, actionTypeRegistry]); + + const connectorFacetButtons = useMemo(() => { + return ( + + + {ACTION_TYPE_MODAL_FILTER_ALL} + + {Object.values(connectorsMap) + .sort((a, b) => a.name.localeCompare(b.name)) + .map(({ actionTypeId, name, total }) => { + return ( + + {name} + + ); + })} + + ); + }, [availableConnectors, connectorsMap, selectedConnectorType, onConnectorOptionSelect]); + + const toggleFilterPopover = useCallback(() => { + setIsConenctorFilterPopoverOpen((prev) => !prev); + }, []); + const closeFilterPopover = useCallback(() => { + setIsConenctorFilterPopoverOpen(false); + }, []); + const connectorFilterButton = useMemo(() => { + const button = ( + + {ACTION_TYPE_MODAL_FILTER_LIST_TITLE} + + ); + + const options: EuiSelectableProps['options'] = Object.values(connectorsMap) + .sort((a, b) => a.name.localeCompare(b.name)) + .map(({ actionTypeId, name }) => ({ + label: name, + checked: selectedConnectorType === actionTypeId ? 'on' : undefined, + onClick: onConnectorOptionSelect(actionTypeId), + })); + + return ( + + + + {(list) =>
{list}
} +
+
+
+ ); + }, [ + closeFilterPopover, + connectorsMap, + isConenctorFilterPopoverOpen, + onConnectorOptionSelect, + toggleFilterPopover, + selectedConnectorType, + ]); + + const connectorCards = useMemo(() => { + if (!filteredConnectors.length) { + return ( + {ACTION_TYPE_MODAL_EMPTY_TITLE}} + body={ + +

{ACTION_TYPE_MODAL_EMPTY_TEXT}

+
+ } + actions={ + + {MODAL_SEARCH_CLEAR_FILTERS_TEXT} + + } + /> + ); + } + return ( + + {filteredConnectors.map((connector) => { + const { id, actionTypeId, name } = connector; + const actionTypeModel = actionTypeRegistry.get(actionTypeId); + const actionType = connectorTypes.find((item) => item.id === actionTypeId); + + if (!actionType) { + return null; + } + + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionType, + preconfiguredConnectors + ); + + const isSystemActionsSelected = Boolean( + actionTypeModel.isSystemActionType && + actions.find((action) => action.actionTypeId === actionTypeModel.id) + ); + + const isDisabled = !checkEnabledResult.isEnabled || isSystemActionsSelected; + + const connectorCard = ( + + }> + + + + } + title={name} + description={ + <> + {actionTypeModel.selectMessage} + + + {actionType?.name} + + + } + onClick={() => onSelectConnectorInternal(connector)} + /> + ); + + return ( + + {checkEnabledResult.isEnabled && connectorCard} + {!checkEnabledResult.isEnabled && ( + + {connectorCard} + + )} + + ); + })} + + ); + }, [ + actions, + preconfiguredConnectors, + filteredConnectors, + actionTypeRegistry, + connectorTypes, + onSelectConnectorInternal, + onClearFilters, + ]); + + return ( + <> + + + + + + + + + {connectorFilterButton} + + + + + + + + + {connectorFacetButtons} + + + {connectorCards} + + + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.test.tsx index d8c183820d3cb..c8e87fe3a3cff 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.test.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.test.tsx @@ -23,18 +23,16 @@ import { jest.mock('../hooks', () => ({ useRuleFormState: jest.fn(), useRuleFormDispatch: jest.fn(), + useRuleFormScreenContext: jest.fn(), })); -const { useRuleFormState, useRuleFormDispatch } = jest.requireMock('../hooks'); +const { useRuleFormState, useRuleFormDispatch, useRuleFormScreenContext } = + jest.requireMock('../hooks'); const mockConnectors: ActionConnector[] = [getConnector('1'), getConnector('2')]; const mockActionTypes: ActionType[] = [getActionType('1'), getActionType('2')]; -const mockOnClose = jest.fn(); - -const mockOnSelectConnector = jest.fn(); - const mockOnChange = jest.fn(); describe('ruleActionsConnectorsModal', () => { @@ -55,6 +53,10 @@ describe('ruleActionsConnectorsModal', () => { aadTemplateFields: [], }); useRuleFormDispatch.mockReturnValue(mockOnChange); + useRuleFormScreenContext.mockReturnValue({ + setIsConnectorsScreenVisible: false, + setIsShowRequestScreenVisible: false, + }); }); afterEach(() => { @@ -62,16 +64,12 @@ describe('ruleActionsConnectorsModal', () => { }); test('renders correctly', () => { - render( - - ); + render(); expect(screen.getByTestId('ruleActionsConnectorsModal')); }); test('should render connectors and filters', () => { - render( - - ); + render(); expect(screen.getByText('connector-1')).toBeInTheDocument(); expect(screen.getByText('connector-2')).toBeInTheDocument(); @@ -86,9 +84,7 @@ describe('ruleActionsConnectorsModal', () => { }); test('should allow for searching of connectors', async () => { - render( - - ); + render(); // Type first connector await userEvent.type(screen.getByTestId('ruleActionsConnectorsModalSearch'), 'connector-1'); @@ -116,9 +112,7 @@ describe('ruleActionsConnectorsModal', () => { }); test('should allow for filtering of connectors', async () => { - render( - - ); + render(); const filterButtonGroup = screen.getByTestId('ruleActionsConnectorsModalFilterButtonGroup'); @@ -134,40 +128,8 @@ describe('ruleActionsConnectorsModal', () => { expect(screen.getAllByTestId('ruleActionsConnectorsModalCard').length).toEqual(2); }); - test('should call onSelectConnector when connector is clicked', async () => { - render( - - ); - - await userEvent.click(screen.getByText('connector-1')); - expect(mockOnSelectConnector).toHaveBeenLastCalledWith({ - actionTypeId: 'actionType-1', - config: { config: 'config-1' }, - id: 'connector-1', - isDeprecated: false, - isPreconfigured: false, - isSystemAction: false, - name: 'connector-1', - secrets: { secret: 'secret' }, - }); - - await userEvent.click(screen.getByText('connector-2')); - expect(mockOnSelectConnector).toHaveBeenLastCalledWith({ - actionTypeId: 'actionType-2', - config: { config: 'config-2' }, - id: 'connector-2', - isDeprecated: false, - isPreconfigured: false, - isSystemAction: false, - name: 'connector-2', - secrets: { secret: 'secret' }, - }); - }); - test('should not render connector if action type doesnt exist', () => { - render( - - ); + render(); expect(screen.queryByText('connector2')).not.toBeInTheDocument(); }); @@ -188,9 +150,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: mockActionTypes, }); - render( - - ); + render(); expect(screen.queryByText('connector2')).not.toBeInTheDocument(); }); @@ -227,9 +187,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: mockActionTypes, }); - render( - - ); + render(); const filterButtonGroup = screen.getByTestId('ruleActionsConnectorsModalFilterButtonGroup'); expect(within(filterButtonGroup).getByText('actionType: 1')).toBeInTheDocument(); expect(within(filterButtonGroup).queryByText('actionType: 2')).not.toBeInTheDocument(); @@ -270,9 +228,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: mockActionTypes, }); - render( - - ); + render(); const filterButtonGroup = screen.getByTestId('ruleActionsConnectorsModalFilterButtonGroup'); await userEvent.click(within(filterButtonGroup).getByText('actionType: 1')); @@ -302,9 +258,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: mockActionTypes, }); - render( - - ); + render(); expect(screen.queryByText('connector-2')).not.toBeInTheDocument(); }); @@ -326,9 +280,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: [getActionType('1'), getActionType('2', { enabledInConfig: false })], }); - render( - - ); + render(); expect(screen.queryByText('connector-2')).not.toBeInTheDocument(); }); @@ -350,9 +302,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: [getActionType('1'), getActionType('2', { enabledInConfig: false })], }); - render( - - ); + render(); expect(screen.getByText('connector-2')).toBeInTheDocument(); }); @@ -374,9 +324,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: [getActionType('1'), getActionType('2', { enabledInLicense: false })], }); - render( - - ); + render(); expect(screen.getByText('connector-2')).toBeDisabled(); }); @@ -399,9 +347,7 @@ describe('ruleActionsConnectorsModal', () => { connectorTypes: [getActionType('1'), getActionType('2', { isSystemActionType: true })], }); - render( - - ); + render(); expect(screen.getByText('connector-2')).toBeDisabled(); }); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.tsx index d411e468a8a83..2eea99329c3cd 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_actions/rule_actions_connectors_modal.tsx @@ -8,317 +8,39 @@ */ import { - EuiButton, - EuiCard, - EuiEmptyPrompt, - EuiFacetButton, - EuiFacetGroup, - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiLoadingSpinner, EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, - EuiSpacer, - EuiText, - EuiToolTip, useCurrentEuiBreakpoint, useEuiTheme, } from '@elastic/eui'; -import { ActionConnector, checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared'; -import React, { Suspense, useCallback, useMemo, useState } from 'react'; -import { useRuleFormState } from '../hooks'; -import { - ACTION_TYPE_MODAL_EMPTY_TEXT, - ACTION_TYPE_MODAL_EMPTY_TITLE, - ACTION_TYPE_MODAL_FILTER_ALL, - ACTION_TYPE_MODAL_TITLE, - MODAL_SEARCH_CLEAR_FILTERS_TEXT, - MODAL_SEARCH_PLACEHOLDER, -} from '../translations'; - -type ConnectorsMap = Record; - -export interface RuleActionsConnectorsModalProps { - onClose: () => void; - onSelectConnector: (connector: ActionConnector) => void; -} - -export const RuleActionsConnectorsModal = (props: RuleActionsConnectorsModalProps) => { - const { onClose, onSelectConnector } = props; - - const [searchValue, setSearchValue] = useState(''); - const [selectedConnectorType, setSelectedConnectorType] = useState('all'); +import React, { useCallback } from 'react'; +import { ACTION_TYPE_MODAL_TITLE } from '../translations'; +import { RuleActionsConnectorsBody } from './rule_actions_connectors_body'; +import { useRuleFormScreenContext } from '../hooks'; +export const RuleActionsConnectorsModal = () => { const { euiTheme } = useEuiTheme(); const currentBreakpoint = useCurrentEuiBreakpoint() ?? 'm'; const isFullscreenPortrait = ['s', 'xs'].includes(currentBreakpoint); - const { - plugins: { actionTypeRegistry }, - formData: { actions }, - connectors, - connectorTypes, - } = useRuleFormState(); - - const preconfiguredConnectors = useMemo(() => { - return connectors.filter((connector) => connector.isPreconfigured); - }, [connectors]); - - const availableConnectors = useMemo(() => { - return connectors.filter(({ actionTypeId }) => { - const actionType = connectorTypes.find(({ id }) => id === actionTypeId); - const actionTypeModel = actionTypeRegistry.get(actionTypeId); - - if (!actionType) { - return false; - } - - if (!actionTypeModel.actionParamsFields) { - return false; - } - - const checkEnabledResult = checkActionFormActionTypeEnabled( - actionType, - preconfiguredConnectors - ); - - if (!actionType.enabledInConfig && !checkEnabledResult.isEnabled) { - return false; - } - - return true; - }); - }, [connectors, connectorTypes, preconfiguredConnectors, actionTypeRegistry]); - - const onSearchChange = useCallback((e: React.ChangeEvent) => { - setSearchValue(e.target.value); - }, []); - - const onConnectorOptionSelect = useCallback( - (id: string) => () => { - setSelectedConnectorType((prev) => { - if (prev === id) { - return ''; - } - return id; - }); - }, - [] - ); - - const onClearFilters = useCallback(() => { - setSearchValue(''); - setSelectedConnectorType('all'); - }, []); - - const connectorsMap: ConnectorsMap | null = useMemo(() => { - return availableConnectors.reduce((result, { actionTypeId }) => { - const actionTypeModel = actionTypeRegistry.get(actionTypeId); - const subtype = actionTypeModel.subtype; - - const shownActionTypeId = actionTypeModel.hideInUi - ? subtype?.filter((type) => type.id !== actionTypeId)[0].id - : undefined; - - const currentActionTypeId = shownActionTypeId ? shownActionTypeId : actionTypeId; - - if (result[currentActionTypeId]) { - result[currentActionTypeId].total += 1; - } else { - result[currentActionTypeId] = { - actionTypeId: currentActionTypeId, - total: 1, - name: connectorTypes.find(({ id }) => id === currentActionTypeId)?.name || '', - }; - } - - return result; - }, {}); - }, [availableConnectors, connectorTypes, actionTypeRegistry]); - - const filteredConnectors = useMemo(() => { - return availableConnectors - .filter(({ actionTypeId }) => { - const subtype = actionTypeRegistry.get(actionTypeId).subtype?.map((type) => type.id); - - if (selectedConnectorType === 'all' || selectedConnectorType === '') { - return true; - } - - if (subtype?.includes(selectedConnectorType)) { - return subtype.includes(actionTypeId); - } - - return selectedConnectorType === actionTypeId; - }) - .filter(({ actionTypeId, name }) => { - const trimmedSearchValue = searchValue.trim().toLocaleLowerCase(); - if (trimmedSearchValue === '') { - return true; - } - const actionTypeModel = actionTypeRegistry.get(actionTypeId); - const actionType = connectorTypes.find(({ id }) => id === actionTypeId); - const textSearchTargets = [ - name.toLocaleLowerCase(), - actionTypeModel.selectMessage?.toLocaleLowerCase(), - actionTypeModel.actionTypeTitle?.toLocaleLowerCase(), - actionType?.name?.toLocaleLowerCase(), - ]; - return textSearchTargets.some((text) => text?.includes(trimmedSearchValue)); - }); - }, [availableConnectors, selectedConnectorType, searchValue, connectorTypes, actionTypeRegistry]); - - const connectorFacetButtons = useMemo(() => { - return ( - - - {ACTION_TYPE_MODAL_FILTER_ALL} - - {Object.values(connectorsMap) - .sort((a, b) => a.name.localeCompare(b.name)) - .map(({ actionTypeId, name, total }) => { - return ( - - {name} - - ); - })} - - ); - }, [availableConnectors, connectorsMap, selectedConnectorType, onConnectorOptionSelect]); - - const connectorCards = useMemo(() => { - if (!filteredConnectors.length) { - return ( - {ACTION_TYPE_MODAL_EMPTY_TITLE}} - body={ - -

{ACTION_TYPE_MODAL_EMPTY_TEXT}

-
- } - actions={ - - {MODAL_SEARCH_CLEAR_FILTERS_TEXT} - - } - /> - ); - } - return ( - - {filteredConnectors.map((connector) => { - const { id, actionTypeId, name } = connector; - const actionTypeModel = actionTypeRegistry.get(actionTypeId); - const actionType = connectorTypes.find((item) => item.id === actionTypeId); - - if (!actionType) { - return null; - } - - const checkEnabledResult = checkActionFormActionTypeEnabled( - actionType, - preconfiguredConnectors - ); - - const isSystemActionsSelected = Boolean( - actionTypeModel.isSystemActionType && - actions.find((action) => action.actionTypeId === actionTypeModel.id) - ); - - const isDisabled = !checkEnabledResult.isEnabled || isSystemActionsSelected; - - const connectorCard = ( - - }> - - - - } - title={name} - description={ - <> - {actionTypeModel.selectMessage} - - - {actionType?.name} - - - } - onClick={() => onSelectConnector(connector)} - /> - ); - - return ( - - {checkEnabledResult.isEnabled && connectorCard} - {!checkEnabledResult.isEnabled && ( - - {connectorCard} - - )} - - ); - })} - - ); - }, [ - actions, - preconfiguredConnectors, - filteredConnectors, - actionTypeRegistry, - connectorTypes, - onSelectConnector, - onClearFilters, - ]); - - const responseiveHeight = isFullscreenPortrait ? 'initial' : '80vh'; + const responsiveHeight = isFullscreenPortrait ? 'initial' : '80vh'; const responsiveOverflow = isFullscreenPortrait ? 'auto' : 'hidden'; + const { setIsConnectorsScreenVisible } = useRuleFormScreenContext(); + const onClose = useCallback(() => { + setIsConnectorsScreenVisible(false); + }, [setIsConnectorsScreenVisible]); + return ( {ACTION_TYPE_MODAL_TITLE} - - - - - - - - - - - - - {connectorFacetButtons} - - {connectorCards} - - - - + + ); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.test.tsx index ec8f85d025fb8..8525ba7b5a057 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.test.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.test.tsx @@ -8,7 +8,7 @@ */ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { RuleFlyout } from './rule_flyout'; import { RULE_FORM_PAGE_RULE_DEFINITION_TITLE_SHORT, @@ -110,30 +110,22 @@ describe('ruleFlyout', () => { render(); fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); - await waitFor(() => - expect(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument() - ); + expect(await screen.findByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); - await waitFor(() => - expect(screen.getByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument() - ); + expect(await screen.findByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')); - await waitFor(() => - expect(screen.getByTestId('ruleFlyoutFooterNextStepButton')).toBeInTheDocument() - ); + expect(await screen.findByTestId('ruleFlyoutFooterNextStepButton')).toBeInTheDocument(); }); test('should call onSave when save button is pressed', async () => { render(); fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); - await waitFor(() => - expect(screen.getByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument() - ); + expect(await screen.findByTestId('ruleFlyoutFooterPreviousStepButton')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('ruleFlyoutFooterNextStepButton')); - await waitFor(() => - expect(screen.getByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument() - ); + expect(await screen.findByTestId('ruleFlyoutFooterSaveButton')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('ruleFlyoutFooterSaveButton')); expect(onSave).toHaveBeenCalledWith({ diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx index f1a873302b823..4262319d4bda3 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout.tsx @@ -8,9 +8,13 @@ */ import { EuiFlyout, EuiPortal } from '@elastic/eui'; -import React from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import type { RuleFormData } from '../types'; +import { RuleFormStepId } from '../constants'; import { RuleFlyoutBody } from './rule_flyout_body'; +import { RuleFlyoutShowRequest } from './rule_flyout_show_request'; +import { useRuleFormScreenContext } from '../hooks'; +import { RuleFlyoutSelectConnector } from './rule_flyout_select_connector'; interface RuleFlyoutProps { isEdit?: boolean; @@ -19,10 +23,39 @@ interface RuleFlyoutProps { onSave: (formData: RuleFormData) => void; } -// Wrapper component for the rule flyout. Currently only displays RuleFlyoutBody, but will be extended to conditionally -// display the Show Request UI or the Action Connector UI. These UIs take over the entire flyout, so we need to swap out -// their body elements entirely to avoid adding another EuiFlyout element to the DOM -export const RuleFlyout = ({ onSave, isEdit, isSaving, onCancel = () => {} }: RuleFlyoutProps) => { +export const RuleFlyout = ({ + onSave, + isEdit = false, + isSaving = false, + onCancel = () => {}, +}: RuleFlyoutProps) => { + const [initialStep, setInitialStep] = useState(undefined); + + const { + isConnectorsScreenVisible, + isShowRequestScreenVisible, + setIsShowRequestScreenVisible, + setIsConnectorsScreenVisible, + } = useRuleFormScreenContext(); + const onCloseConnectorsScreen = useCallback(() => { + setInitialStep(RuleFormStepId.ACTIONS); + setIsConnectorsScreenVisible(false); + }, [setIsConnectorsScreenVisible]); + + const onOpenShowRequest = useCallback( + () => setIsShowRequestScreenVisible(true), + [setIsShowRequestScreenVisible] + ); + const onCloseShowRequest = useCallback(() => { + setInitialStep(RuleFormStepId.DETAILS); + setIsShowRequestScreenVisible(false); + }, [setIsShowRequestScreenVisible]); + + const hideCloseButton = useMemo( + () => isShowRequestScreenVisible || isConnectorsScreenVisible, + [isConnectorsScreenVisible, isShowRequestScreenVisible] + ); + return ( {} }: Ru size="m" maxWidth={500} className="ruleFormFlyout__container" + hideCloseButton={hideCloseButton} > - + {isShowRequestScreenVisible ? ( + + ) : isConnectorsScreenVisible ? ( + + ) : ( + + )} ); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx index ec5590b3a587b..62244c5629a98 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_body.tsx @@ -28,19 +28,24 @@ import { hasRuleErrors } from '../validation'; import { RuleFlyoutCreateFooter } from './rule_flyout_create_footer'; import { RuleFlyoutEditFooter } from './rule_flyout_edit_footer'; import { RuleFlyoutEditTabs } from './rule_flyout_edit_tabs'; +import { RuleFormStepId } from '../constants'; interface RuleFlyoutBodyProps { isEdit?: boolean; isSaving?: boolean; onCancel: () => void; onSave: (formData: RuleFormData) => void; + onShowRequest: () => void; + initialStep?: RuleFormStepId; } export const RuleFlyoutBody = ({ isEdit = false, isSaving = false, + initialStep, onCancel, onSave, + onShowRequest, }: RuleFlyoutBodyProps) => { const { formData, @@ -77,7 +82,7 @@ export const RuleFlyoutBody = ({ goToPreviousStep, hasNextStep, hasPreviousStep, - } = useRuleFormHorizontalSteps(); + } = useRuleFormHorizontalSteps(initialStep); const { actions } = formData; @@ -133,7 +138,7 @@ export const RuleFlyoutBody = ({ {} /* TODO */} + onShowRequest={onShowRequest} isSaving={isSaving} hasErrors={hasErrors} /> @@ -141,7 +146,7 @@ export const RuleFlyoutBody = ({ {} /* TODO */} + onShowRequest={onShowRequest} goToNextStep={goToNextStep} goToPreviousStep={goToPreviousStep} isSaving={isSaving} diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_select_connector.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_select_connector.tsx new file mode 100644 index 0000000000000..1f17fb6435a49 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_select_connector.tsx @@ -0,0 +1,65 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React from 'react'; +import { + ACTION_TYPE_MODAL_TITLE, + RULE_FLYOUT_FOOTER_BACK_TEXT, + RULE_FLYOUT_HEADER_BACK_TEXT, +} from '../translations'; +import { RuleActionsConnectorsBody } from '../rule_actions/rule_actions_connectors_body'; + +interface RuleFlyoutSelectConnectorProps { + onClose: () => void; +} +export const RuleFlyoutSelectConnector = ({ onClose }: RuleFlyoutSelectConnectorProps) => { + return ( + <> + + + + + + + +

{ACTION_TYPE_MODAL_TITLE}

+
+
+
+
+ + + + + + {RULE_FLYOUT_FOOTER_BACK_TEXT} + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_show_request.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_show_request.tsx new file mode 100644 index 0000000000000..fa6c14f996316 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_flyout/rule_flyout_show_request.tsx @@ -0,0 +1,73 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiText, + EuiTitle, + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import React from 'react'; +import { + SHOW_REQUEST_MODAL_SUBTITLE, + SHOW_REQUEST_MODAL_TITLE, + RULE_FLYOUT_FOOTER_BACK_TEXT, + RULE_FLYOUT_HEADER_BACK_TEXT, +} from '../translations'; +import { RequestCodeBlock } from '../request_code_block'; + +interface RuleFlyoutShowRequestProps { + isEdit: boolean; + onClose: () => void; +} +export const RuleFlyoutShowRequest = ({ isEdit, onClose }: RuleFlyoutShowRequestProps) => { + return ( + <> + + + + + + + +

{SHOW_REQUEST_MODAL_TITLE(isEdit)}

+
+
+
+
+ +

+ {SHOW_REQUEST_MODAL_SUBTITLE(isEdit)} +

+ + +
+ + + {RULE_FLYOUT_FOOTER_BACK_TEXT} + + + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss index fd905ed8f9d04..0564dc5847979 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.scss @@ -6,7 +6,11 @@ container-type: inline-size; } -@container (max-width: 768px) { +.actionConnectorModal__container { + container-type: inline-size; +} + +@container (max-width: 767px) { .euiDescribedFormGroup { flex-direction: column; } @@ -24,4 +28,28 @@ .ruleDefinitionHeaderRuleTypeDescription, .ruleDefinitionHeaderDocsLink { font-size: $euiFontSizeS; } +} + +[class*='showForContainer'] { + display: none; +} + +@container (max-width: 767px) and (min-width: 575px) { + .hideForContainer--s { + display: none; + } + + .showForContainer--s { + display: initial !important; + } +} + +@container (max-width: 574px) { + .hideForContainer--xs { + display: none; + } + + .showForContainer--xs { + display: initial !important; + } } \ No newline at end of file diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx index 5b3f43a5bd4ba..61ef0d775d505 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form.tsx @@ -19,6 +19,7 @@ import { } from './translations'; import { RuleFormPlugins } from './types'; import './rule_form.scss'; +import { RuleFormScreenContextProvider } from './rule_form_screen_context'; const queryClient = new QueryClient(); @@ -117,7 +118,9 @@ export const RuleForm = (props: RuleFormProps) => { return ( -
{ruleFormComponent}
+ +
{ruleFormComponent}
+
); }; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/index.ts b/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/index.ts new file mode 100644 index 0000000000000..1804a351dcd40 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export * from './rule_form_screen_context'; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/rule_form_screen_context.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/rule_form_screen_context.tsx new file mode 100644 index 0000000000000..15c346266c922 --- /dev/null +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_form_screen_context/rule_form_screen_context.tsx @@ -0,0 +1,41 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { createContext, useState } from 'react'; + +/* + * A generic wrapper for keeping track of which screens to show on top of the Rule Form + * This provides logic that works on both the Rule Page, which displays these screens in a modal, + * and the Rule Flyout, which displays these screens by replacing the entire content of the flyout. + */ +const initialRuleFormScreenContextState = { + isConnectorsScreenVisible: false, + isShowRequestScreenVisible: false, + setIsConnectorsScreenVisible: (show: boolean) => {}, + setIsShowRequestScreenVisible: (show: boolean) => {}, +}; + +export const RuleFormScreenContext = createContext(initialRuleFormScreenContextState); + +export const RuleFormScreenContextProvider: React.FC = ({ children }) => { + const [isConnectorsScreenVisible, setIsConnectorsScreenVisible] = useState(false); + const [isShowRequestScreenVisible, setIsShowRequestScreenVisible] = useState(false); + return ( + + {children} + + ); +}; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page.tsx index 52c25ee79a5d8..5479d552f2b1a 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared'; import React, { useCallback, useMemo, useState } from 'react'; -import { useRuleFormState, useRuleFormSteps } from '../hooks'; +import { useRuleFormScreenContext, useRuleFormState, useRuleFormSteps } from '../hooks'; import { DISABLED_ACTIONS_WARNING_TITLE, RULE_FORM_CANCEL_MODAL_CANCEL, @@ -32,6 +32,8 @@ import { import type { RuleFormData } from '../types'; import { RulePageFooter } from './rule_page_footer'; import { RulePageNameInput } from './rule_page_name_input'; +import { RuleActionsConnectorsModal } from '../rule_actions/rule_actions_connectors_modal'; +import { RulePageShowRequestModal } from './rule_page_show_request_modal'; export interface RulePageProps { isEdit?: boolean; @@ -68,6 +70,8 @@ export const RulePage = (props: RulePageProps) => { } }, [touched, onCancel]); + const { isConnectorsScreenVisible, isShowRequestScreenVisible } = useRuleFormScreenContext(); + const hasActionsDisabled = useMemo(() => { const preconfiguredConnectors = connectors.filter((connector) => connector.isPreconfigured); return actions.some((action) => { @@ -149,6 +153,8 @@ export const RulePage = (props: RulePageProps) => {

{RULE_FORM_CANCEL_MODAL_DESCRIPTION}

)} + {isConnectorsScreenVisible && } + {isShowRequestScreenVisible && } ); }; diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.test.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.test.tsx index d937c60aa3a52..adf54ed9fb55f 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.test.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.test.tsx @@ -23,16 +23,19 @@ jest.mock('../validation/validate_form', () => ({ jest.mock('../hooks', () => ({ useRuleFormState: jest.fn(), + useRuleFormScreenContext: jest.fn(), })); const { hasRuleErrors } = jest.requireMock('../validation/validate_form'); -const { useRuleFormState } = jest.requireMock('../hooks'); +const { useRuleFormState, useRuleFormScreenContext } = jest.requireMock('../hooks'); const onSave = jest.fn(); const onCancel = jest.fn(); hasRuleErrors.mockReturnValue(false); +const mockSetIsShowRequestScreenVisible = jest.fn(); + describe('rulePageFooter', () => { beforeEach(() => { useRuleFormState.mockReturnValue({ @@ -51,6 +54,9 @@ describe('rulePageFooter', () => { actions: [], }, }); + useRuleFormScreenContext.mockReturnValue({ + setIsShowRequestScreenVisible: mockSetIsShowRequestScreenVisible, + }); }); afterEach(() => { @@ -77,7 +83,7 @@ describe('rulePageFooter', () => { render(); fireEvent.click(screen.getByTestId('rulePageFooterShowRequestButton')); - expect(screen.getByTestId('rulePageShowRequestModal')).toBeInTheDocument(); + expect(mockSetIsShowRequestScreenVisible).toHaveBeenCalledWith(true); }); test('should show create rule confirmation', () => { diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.tsx index 62a0e4b64e4f1..375d4c320c205 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_footer.tsx @@ -15,9 +15,8 @@ import { RULE_PAGE_FOOTER_CREATE_TEXT, RULE_PAGE_FOOTER_SAVE_TEXT, } from '../translations'; -import { useRuleFormState } from '../hooks'; +import { useRuleFormScreenContext, useRuleFormState } from '../hooks'; import { hasRuleErrors } from '../validation'; -import { RulePageShowRequestModal } from './rule_page_show_request_modal'; import { RulePageConfirmCreateRule } from './rule_page_confirm_create_rule'; export interface RulePageFooterProps { @@ -28,9 +27,10 @@ export interface RulePageFooterProps { } export const RulePageFooter = (props: RulePageFooterProps) => { - const [showRequestModal, setShowRequestModal] = useState(false); const [showCreateConfirmation, setShowCreateConfirmation] = useState(false); + const { setIsShowRequestScreenVisible } = useRuleFormScreenContext(); + const { isEdit = false, isSaving = false, onCancel, onSave } = props; const { @@ -68,12 +68,8 @@ export const RulePageFooter = (props: RulePageFooterProps) => { }, [isEdit]); const onOpenShowRequestModalClick = useCallback(() => { - setShowRequestModal(true); - }, []); - - const onCloseShowRequestModalClick = useCallback(() => { - setShowRequestModal(false); - }, []); + setIsShowRequestScreenVisible(true); + }, [setIsShowRequestScreenVisible]); const onSaveClick = useCallback(() => { if (isEdit) { @@ -134,9 +130,6 @@ export const RulePageFooter = (props: RulePageFooterProps) => { - {showRequestModal && ( - - )} {showCreateConfirmation && ( ({ useRuleFormState: jest.fn(), + useRuleFormScreenContext: jest.fn(), })); -const { useRuleFormState } = jest.requireMock('../hooks'); +const { useRuleFormState, useRuleFormScreenContext } = jest.requireMock('../hooks'); const formData: RuleFormData = { params: { @@ -46,6 +47,13 @@ const formData: RuleFormData = { const onCloseMock = jest.fn(); describe('rulePageShowRequestModal', () => { + beforeEach(() => { + useRuleFormScreenContext.mockReturnValue({ + isShowRequestScreenVisible: false, + setIsShowRequestScreenVisible: onCloseMock, + }); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -53,7 +61,7 @@ describe('rulePageShowRequestModal', () => { test('renders create request correctly', async () => { useRuleFormState.mockReturnValue({ formData, multiConsumerSelection: 'logs' }); - render(); + render(); expect(screen.getByTestId('modalHeaderTitle').textContent).toBe('Create alerting rule request'); expect(screen.getByTestId('modalSubtitle').textContent).toBe( @@ -103,7 +111,7 @@ describe('rulePageShowRequestModal', () => { id: 'test-id', }); - render(); + render(); expect(screen.getByTestId('modalHeaderTitle').textContent).toBe('Edit alerting rule request'); expect(screen.getByTestId('modalSubtitle').textContent).toBe( @@ -151,7 +159,7 @@ describe('rulePageShowRequestModal', () => { id: 'test-id', }); - render(); + render(); fireEvent.click(screen.getByLabelText('Closes this modal window')); expect(onCloseMock).toHaveBeenCalled(); }); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_show_request_modal.tsx b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_show_request_modal.tsx index 49d2f08fc60ab..b9adc3ca4ead1 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_show_request_modal.tsx +++ b/src/platform/packages/shared/response-ops/rule_form/src/rule_page/rule_page_show_request_modal.tsx @@ -7,67 +7,32 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useMemo } from 'react'; -import { pick, omit } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { + EuiFlexGroup, + EuiFlexItem, EuiModal, + EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, - EuiModalBody, - EuiCodeBlock, EuiText, EuiTextColor, - EuiFlexGroup, - EuiFlexItem, } from '@elastic/eui'; -import { BASE_ALERTING_API_PATH } from '../constants'; -import { RuleFormData } from '../types'; -import { - CreateRuleBody, - UPDATE_FIELDS_WITH_ACTIONS, - UpdateRuleBody, - transformCreateRuleBody, - transformUpdateRuleBody, -} from '../common/apis'; -import { useRuleFormState } from '../hooks'; - -const stringifyBodyRequest = ({ - formData, - isEdit, -}: { - formData: RuleFormData; - isEdit: boolean; -}): string => { - try { - const request = isEdit - ? transformUpdateRuleBody(pick(formData, UPDATE_FIELDS_WITH_ACTIONS) as UpdateRuleBody) - : transformCreateRuleBody(omit(formData, 'id') as CreateRuleBody); - return JSON.stringify(request, null, 2); - } catch { - return SHOW_REQUEST_MODAL_ERROR; - } -}; +import React, { useCallback } from 'react'; +import { RequestCodeBlock } from '../request_code_block'; +import { SHOW_REQUEST_MODAL_SUBTITLE, SHOW_REQUEST_MODAL_TITLE } from '../translations'; +import { useRuleFormScreenContext } from '../hooks'; export interface RulePageShowRequestModalProps { - onClose: () => void; isEdit?: boolean; } export const RulePageShowRequestModal = (props: RulePageShowRequestModalProps) => { - const { onClose, isEdit = false } = props; + const { isEdit = false } = props; + const { setIsShowRequestScreenVisible } = useRuleFormScreenContext(); - const { formData, id, multiConsumerSelection } = useRuleFormState(); - - const formattedRequest = useMemo(() => { - return stringifyBodyRequest({ - formData: { - ...formData, - ...(multiConsumerSelection ? { consumer: multiConsumerSelection } : {}), - }, - isEdit, - }); - }, [formData, isEdit, multiConsumerSelection]); + const onClose = useCallback(() => { + setIsShowRequestScreenVisible(false); + }, [setIsShowRequestScreenVisible]); return ( - - {`${isEdit ? 'PUT' : 'POST'} kbn:${BASE_ALERTING_API_PATH}/rule${ - isEdit ? `/${id}` : '' - }\n${formattedRequest}`} - + ); }; - -const SHOW_REQUEST_MODAL_EDIT = i18n.translate( - 'responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitleEdit', - { - defaultMessage: 'edit', - } -); - -const SHOW_REQUEST_MODAL_CREATE = i18n.translate( - 'responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitleCreate', - { - defaultMessage: 'create', - } -); - -const SHOW_REQUEST_MODAL_SUBTITLE = (edit: boolean) => - i18n.translate('responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitle', { - defaultMessage: 'This Kibana request will {requestType} this rule.', - values: { requestType: edit ? SHOW_REQUEST_MODAL_EDIT : SHOW_REQUEST_MODAL_CREATE }, - }); - -const SHOW_REQUEST_MODAL_TITLE_EDIT = i18n.translate( - 'responseOpsRuleForm.ruleForm.showRequestModal.headerTitleEdit', - { - defaultMessage: 'Edit', - } -); - -const SHOW_REQUEST_MODAL_TITLE_CREATE = i18n.translate( - 'responseOpsRuleForm.ruleForm.showRequestModal.headerTitleCreate', - { - defaultMessage: 'Create', - } -); - -const SHOW_REQUEST_MODAL_TITLE = (edit: boolean) => - i18n.translate('responseOpsRuleForm.ruleForm.showRequestModal.headerTitle', { - defaultMessage: '{requestType} alerting rule request', - values: { - requestType: edit ? SHOW_REQUEST_MODAL_TITLE_EDIT : SHOW_REQUEST_MODAL_TITLE_CREATE, - }, - }); - -const SHOW_REQUEST_MODAL_ERROR = i18n.translate( - 'responseOpsRuleForm.ruleForm.showRequestModal.somethingWentWrongDescription', - { - defaultMessage: 'Sorry about that, something went wrong.', - } -); diff --git a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts index eebe9dd2157c3..91cac8e9e99f2 100644 --- a/src/platform/packages/shared/response-ops/rule_form/src/translations.ts +++ b/src/platform/packages/shared/response-ops/rule_form/src/translations.ts @@ -343,6 +343,13 @@ export const RULE_FLYOUT_HEADER_EDIT_TITLE = i18n.translate( } ); +export const RULE_FLYOUT_HEADER_BACK_TEXT = i18n.translate( + 'responseOpsRuleForm.ruleForm.ruleFlyoutHeader.backText', + { + defaultMessage: 'Back', + } +); + export const RULE_FLYOUT_FOOTER_CANCEL_TEXT = i18n.translate( 'responseOpsRuleForm.ruleForm.ruleFlyoutFooter.cancelText', { @@ -663,6 +670,13 @@ export const ACTION_TYPE_MODAL_FILTER_ALL = i18n.translate( } ); +export const ACTION_TYPE_MODAL_FILTER_LIST_TITLE = i18n.translate( + 'responseOpsRuleForm.ruleForm.actionTypeModalFilterListTitle', + { + defaultMessage: 'Filter', + } +); + export const ACTION_TYPE_MODAL_EMPTY_TITLE = i18n.translate( 'responseOpsRuleForm.ruleForm.actionTypeModalEmptyTitle', { @@ -730,3 +744,52 @@ export const DISABLED_ACTIONS_WARNING_TITLE = i18n.translate( defaultMessage: 'This rule has actions that are disabled', } ); + +export const SHOW_REQUEST_MODAL_EDIT = i18n.translate( + 'responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitleEdit', + { + defaultMessage: 'edit', + } +); + +export const SHOW_REQUEST_MODAL_CREATE = i18n.translate( + 'responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitleCreate', + { + defaultMessage: 'create', + } +); + +export const SHOW_REQUEST_MODAL_SUBTITLE = (edit: boolean) => + i18n.translate('responseOpsRuleForm.ruleForm.showRequestModal.subheadingTitle', { + defaultMessage: 'This Kibana request will {requestType} this rule.', + values: { requestType: edit ? SHOW_REQUEST_MODAL_EDIT : SHOW_REQUEST_MODAL_CREATE }, + }); + +export const SHOW_REQUEST_MODAL_TITLE_EDIT = i18n.translate( + 'responseOpsRuleForm.ruleForm.showRequestModal.headerTitleEdit', + { + defaultMessage: 'Edit', + } +); + +export const SHOW_REQUEST_MODAL_TITLE_CREATE = i18n.translate( + 'responseOpsRuleForm.ruleForm.showRequestModal.headerTitleCreate', + { + defaultMessage: 'Create', + } +); + +export const SHOW_REQUEST_MODAL_TITLE = (edit: boolean) => + i18n.translate('responseOpsRuleForm.ruleForm.showRequestModal.headerTitle', { + defaultMessage: '{requestType} alerting rule request', + values: { + requestType: edit ? SHOW_REQUEST_MODAL_TITLE_EDIT : SHOW_REQUEST_MODAL_TITLE_CREATE, + }, + }); + +export const SHOW_REQUEST_MODAL_ERROR = i18n.translate( + 'responseOpsRuleForm.ruleForm.showRequestModal.somethingWentWrongDescription', + { + defaultMessage: 'Sorry about that, something went wrong.', + } +); From 3fde9d6ae99a5d7860b3561a660d2a67140ca7fa Mon Sep 17 00:00:00 2001 From: Sean Story Date: Wed, 22 Jan 2025 16:33:59 -0600 Subject: [PATCH 10/12] check for active fleet servers (#207920) ## Summary The Upgrade Assistant deprecations for Native Connectors -> Agentless Connectors care about if Fleet Server is _actively_ running, but it seems that the check we're using is whether one was _ever_ running. This change introduces an optional `activeOnly` param to `hasFleetServer`, and uses it for these deprecations. --- .../shared/fleet/server/services/fleet_server/index.ts | 9 ++++++--- .../enterprise_search/server/deprecations/index.ts | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/platform/plugins/shared/fleet/server/services/fleet_server/index.ts b/x-pack/platform/plugins/shared/fleet/server/services/fleet_server/index.ts index e596709523351..48f43eab7c709 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/fleet_server/index.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/fleet_server/index.ts @@ -86,16 +86,19 @@ export const hasFleetServersForPolicies = async ( }; /** - * Check if at least one fleet server agent exists, regardless of its online status + * Check if at least one fleet server agent exists. + * `activeOnly` flag can be used to filter only active agents. */ export async function hasFleetServers( esClient: ElasticsearchClient, - soClient: SavedObjectsClientContract + soClient: SavedObjectsClientContract, + activeOnly: boolean = false ) { return await hasFleetServersForPolicies( esClient, soClient, - await getFleetServerPolicies(soClient) + await getFleetServerPolicies(soClient), + activeOnly ); } diff --git a/x-pack/solutions/search/plugins/enterprise_search/server/deprecations/index.ts b/x-pack/solutions/search/plugins/enterprise_search/server/deprecations/index.ts index 6d869a73ca698..4f5807dc32958 100644 --- a/x-pack/solutions/search/plugins/enterprise_search/server/deprecations/index.ts +++ b/x-pack/solutions/search/plugins/enterprise_search/server/deprecations/index.ts @@ -55,7 +55,8 @@ export const getRegisteredDeprecations = ( const hasAgentless = isAgentlessEnabled(); const hasFleetServer = await hasFleetServers( ctx.esClient.asInternalUser, - ctx.savedObjectsClient + ctx.savedObjectsClient, + true // only counts if the Fleet Server is active ); const entSearchDetails = getEnterpriseSearchNodeDeprecation(config, cloud, docsUrl); const [crawlerDetails, nativeConnectorsDetails] = await Promise.all([ From fbc7d7f6e4161a40ac04967b550f405d5d0ceb7c Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:55:01 +1100 Subject: [PATCH 11/12] [8.x] [ResponseOps][Cases]Fix unit tests for React@18 (#207072) (#207848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps][Cases]Fix unit tests for React@18 (#207072)](https://github.com/elastic/kibana/pull/207072) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Georgiana-Andreea Onoleață Co-authored-by: Elastic Machine --- .../connector_form.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx index cf12e2ce38af4..0883f477066c6 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx @@ -102,21 +102,21 @@ describe('ConnectorForm', () => { /> ); - expect(result.getByTestId('nameInput')).toBeInTheDocument(); + expect(await result.findByTestId('nameInput')).toBeInTheDocument(); await act(async () => { const submit = onChange.mock.calls[0][0].submit; await submit(); }); - await waitFor(() => expect(onChange).toHaveBeenCalled()); - - expect(onChange).toHaveBeenCalledWith({ - isSubmitted: false, - isSubmitting: false, - isValid: false, - preSubmitValidator: expect.anything(), - submit: expect.anything(), + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ + isSubmitted: true, + isSubmitting: false, + isValid: false, + preSubmitValidator: expect.anything(), + submit: expect.anything(), + }); }); }); From e177f1576ecea2577cf0e59a62c9b0bff50c5fd8 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:19:21 +1100 Subject: [PATCH 12/12] [8.x] Update dependency @redocly/cli to ^1.27.2 (main) (#207527) (#207948) # Backport This will backport the following commits from `main` to `8.x`: - [Update dependency @redocly/cli to ^1.27.2 (main) (#207527)](https://github.com/elastic/kibana/pull/207527) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- oas_docs/package-lock.json | 24 ++++++++++++------------ oas_docs/package.json | 2 +- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/oas_docs/package-lock.json b/oas_docs/package-lock.json index aaf2085ec617c..d63ff3078847b 100644 --- a/oas_docs/package-lock.json +++ b/oas_docs/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@redocly/cli": "^1.27.1", + "@redocly/cli": "^1.27.2", "bump-cli": "^2.8.4" } }, @@ -515,12 +515,12 @@ } }, "node_modules/@redocly/cli": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-1.27.1.tgz", - "integrity": "sha512-IgFzIKgWDaGY8jOlWYVp7VyIwElIjqrVLvZWzdDS/vdQlJ0DdISQ1nRy/YSh0Rqw69hJOpgn5cXMH/SS/qO33Q==", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-1.27.2.tgz", + "integrity": "sha512-bJi3Hb3eaTo7lnMXAJ3HQwQS45vrNXwaqNwh/nbgjkzeb4/5p7GXgB6nWkakwKUDiPVMX2rBoa8MCodNaaQ3Yg==", "license": "MIT", "dependencies": { - "@redocly/openapi-core": "1.27.1", + "@redocly/openapi-core": "1.27.2", "abort-controller": "^3.0.0", "chokidar": "^3.5.1", "colorette": "^1.2.0", @@ -550,19 +550,19 @@ } }, "node_modules/@redocly/config": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.17.1.tgz", - "integrity": "sha512-CEmvaJuG7pm2ylQg53emPmtgm4nW2nxBgwXzbVEHpGas/lGnMyN8Zlkgiz6rPw0unASg6VW3wlz27SOL5XFHYQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.20.1.tgz", + "integrity": "sha512-TYiTDtuItiv95YMsrRxyCs1HKLrDPtTvpaD3+kDKXBnFDeJuYKZ+eHXpCr6YeN4inxfVBs7DLhHsQcs9srddyQ==", "license": "MIT" }, "node_modules/@redocly/openapi-core": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.27.1.tgz", - "integrity": "sha512-zQ47/A+Drk2Y75/af69MD3Oad4H9LxkUDzcm7XBkyLNDKIWQrDKDnS5476oDq77+zciymNxgMVtxxVXlnGS8kw==", + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.27.2.tgz", + "integrity": "sha512-qVrDc27DHpeO2NRCMeRdb4299nijKQE3BY0wrA+WUHlOLScorIi/y7JzammLk22IaTvjR9Mv9aTAdjE1aUwJnA==", "license": "MIT", "dependencies": { "@redocly/ajv": "^8.11.2", - "@redocly/config": "^0.17.0", + "@redocly/config": "^0.20.1", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.4", "js-levenshtein": "^1.1.6", diff --git a/oas_docs/package.json b/oas_docs/package.json index 677d34ea66a43..4d82b1891aafe 100644 --- a/oas_docs/package.json +++ b/oas_docs/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "bump-cli": "^2.8.4", - "@redocly/cli": "^1.27.1" + "@redocly/cli": "^1.27.2" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/package.json b/package.json index 1161f29eb265a..aae0e4a9b7ae6 100644 --- a/package.json +++ b/package.json @@ -1528,7 +1528,7 @@ "@octokit/rest": "^17.11.2", "@parcel/watcher": "^2.1.0", "@playwright/test": "1.49.0", - "@redocly/cli": "^1.27.1", + "@redocly/cli": "^1.27.2", "@statoscope/webpack-plugin": "^5.28.2", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", diff --git a/yarn.lock b/yarn.lock index e7dd30222fa72..794a741ee7c85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9227,12 +9227,12 @@ require-from-string "^2.0.2" uri-js-replace "^1.0.1" -"@redocly/cli@^1.27.1": - version "1.27.1" - resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.27.1.tgz#7ae671615c2f6e6d049fce1affe3aa19f308b5a6" - integrity sha512-IgFzIKgWDaGY8jOlWYVp7VyIwElIjqrVLvZWzdDS/vdQlJ0DdISQ1nRy/YSh0Rqw69hJOpgn5cXMH/SS/qO33Q== +"@redocly/cli@^1.27.2": + version "1.27.2" + resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.27.2.tgz#3158df763ef8bbb2dfea312e0840ddf02196fa2c" + integrity sha512-bJi3Hb3eaTo7lnMXAJ3HQwQS45vrNXwaqNwh/nbgjkzeb4/5p7GXgB6nWkakwKUDiPVMX2rBoa8MCodNaaQ3Yg== dependencies: - "@redocly/openapi-core" "1.27.1" + "@redocly/openapi-core" "1.27.2" abort-controller "^3.0.0" chokidar "^3.5.1" colorette "^1.2.0" @@ -9252,18 +9252,18 @@ styled-components "^6.0.7" yargs "17.0.1" -"@redocly/config@^0.17.0": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.17.1.tgz#2def04cecf440dd78c0f102f53f3444fac050768" - integrity sha512-CEmvaJuG7pm2ylQg53emPmtgm4nW2nxBgwXzbVEHpGas/lGnMyN8Zlkgiz6rPw0unASg6VW3wlz27SOL5XFHYQ== +"@redocly/config@^0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.20.1.tgz#867e187d8113d0646eab7859c7835ed0656d8315" + integrity sha512-TYiTDtuItiv95YMsrRxyCs1HKLrDPtTvpaD3+kDKXBnFDeJuYKZ+eHXpCr6YeN4inxfVBs7DLhHsQcs9srddyQ== -"@redocly/openapi-core@1.27.1", "@redocly/openapi-core@^1.4.0": - version "1.27.1" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.27.1.tgz#53b6b6be0ffecf696a1da5aee84fe989cdf6d764" - integrity sha512-zQ47/A+Drk2Y75/af69MD3Oad4H9LxkUDzcm7XBkyLNDKIWQrDKDnS5476oDq77+zciymNxgMVtxxVXlnGS8kw== +"@redocly/openapi-core@1.27.2", "@redocly/openapi-core@^1.4.0": + version "1.27.2" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.27.2.tgz#109163901fd8a2853e805877fe234b65e3c5753a" + integrity sha512-qVrDc27DHpeO2NRCMeRdb4299nijKQE3BY0wrA+WUHlOLScorIi/y7JzammLk22IaTvjR9Mv9aTAdjE1aUwJnA== dependencies: "@redocly/ajv" "^8.11.2" - "@redocly/config" "^0.17.0" + "@redocly/config" "^0.20.1" colorette "^1.2.0" https-proxy-agent "^7.0.4" js-levenshtein "^1.1.6"