diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml
index a2390fa2bd27f..dbe529596102e 100644
--- a/.buildkite/ftr_security_stateful_configs.yml
+++ b/.buildkite/ftr_security_stateful_configs.yml
@@ -69,10 +69,14 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/ess.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts
- - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts
- - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/ess.config.ts
+ - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/sources/indices/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/configs/ess.config.ts
diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml
index f7e5290717cb3..1146a9280ac4e 100644
--- a/config/serverless.oblt.yml
+++ b/config/serverless.oblt.yml
@@ -144,7 +144,7 @@ xpack.uptime.service.tls.key: /mnt/elastic-internal/http-certs/tls.key
# Fleet specific configuration
xpack.fleet.internal.registry.capabilities: ['apm', 'observability', 'uptime']
xpack.fleet.internal.registry.spec.min: '3.0'
-xpack.fleet.internal.registry.spec.max: '3.2'
+xpack.fleet.internal.registry.spec.max: '3.3'
xpack.fleet.internal.registry.kibanaVersionCheckEnabled: false
xpack.fleet.internal.registry.excludePackages: [
# Security integrations
diff --git a/config/serverless.security.yml b/config/serverless.security.yml
index fe86a864d5cf3..5057fa193bef4 100644
--- a/config/serverless.security.yml
+++ b/config/serverless.security.yml
@@ -76,7 +76,7 @@ telemetry.labels.serverless: security
# Fleet specific configuration
xpack.fleet.internal.registry.capabilities: ['security']
xpack.fleet.internal.registry.spec.min: '3.0'
-xpack.fleet.internal.registry.spec.max: '3.2'
+xpack.fleet.internal.registry.spec.max: '3.3'
xpack.fleet.internal.registry.kibanaVersionCheckEnabled: false
xpack.fleet.internal.registry.excludePackages: [
# Oblt integrations
diff --git a/dev_docs/shared_ux/browser_snapshots_filter1.png b/dev_docs/shared_ux/browser_snapshots_filter1.png
new file mode 100644
index 0000000000000..75b04ac16889c
Binary files /dev/null and b/dev_docs/shared_ux/browser_snapshots_filter1.png differ
diff --git a/dev_docs/shared_ux/browser_snapshots_filter2.png b/dev_docs/shared_ux/browser_snapshots_filter2.png
new file mode 100644
index 0000000000000..4eb960ca3b0ed
Binary files /dev/null and b/dev_docs/shared_ux/browser_snapshots_filter2.png differ
diff --git a/dev_docs/shared_ux/browser_snapshots_listing.png b/dev_docs/shared_ux/browser_snapshots_listing.png
new file mode 100644
index 0000000000000..076946c097403
Binary files /dev/null and b/dev_docs/shared_ux/browser_snapshots_listing.png differ
diff --git a/dev_docs/shared_ux/chromium_version_command.png b/dev_docs/shared_ux/chromium_version_command.png
new file mode 100644
index 0000000000000..eb1fc2f7aa050
Binary files /dev/null and b/dev_docs/shared_ux/chromium_version_command.png differ
diff --git a/dev_docs/shared_ux/shared_ux_landing.mdx b/dev_docs/shared_ux/shared_ux_landing.mdx
index d96798eefa61f..6093b1c5c943f 100644
--- a/dev_docs/shared_ux/shared_ux_landing.mdx
+++ b/dev_docs/shared_ux/shared_ux_landing.mdx
@@ -51,6 +51,11 @@ layout: landing
title: 'Reporting / Screenshotting',
description: 'Learn how to integrate your plugin with reporting',
},
+ {
+ pageId: 'kibDevDocsUpdatingPuppeteerAndChromium',
+ title: 'Reporting / Updating Puppeteer and Chromium',
+ description: 'Learn how to update the Puppeteer node module and build headless Chromium',
+ },
{
pageId: 'kibDevTutorialAdvancedSettings',
title: 'Advanced Settings (uiSettings)',
diff --git a/dev_docs/shared_ux/updating_puppeteer_and_chromium.mdx b/dev_docs/shared_ux/updating_puppeteer_and_chromium.mdx
new file mode 100644
index 0000000000000..75341b5397566
--- /dev/null
+++ b/dev_docs/shared_ux/updating_puppeteer_and_chromium.mdx
@@ -0,0 +1,136 @@
+---
+id: kibDevDocsUpdatingPuppeteerAndChromium
+slug: /kibana-dev-docs/updating-puppeteer-and-chromium
+title: Updating Puppeteer and Chromium
+description: Describes the process to update the Puppeteer node module and build a compatible version of Chromium
+tags: ['kibana', 'dev', 'puppeteer', 'chromium', 'reporting', 'screenshotting']
+---
+
+# Updating Puppeteer and Chromium for Kibana
+
+This document builds off of [Keeping Chromium Up-To-Date](https://docs.google.com/presentation/d/19Z6ocVSoNnvY_wPjEG6Wstt-sOuIYkkNr4jqAqT6TGo/edit#slide=id.g6eae63e93f_4_112)
+
+## 1. **Installing new puppeteer version**
+
+- Determine the version for the current latest release by checking [here](https://github.com/puppeteer/puppeteer/releases).
+- We can go ahead and install the current version within kibana like so; `yarn add puppeteer@23.3.1` if we assume for the sake of this guide that the latest version is `23.3.1`, on installing this, we'd also want to run `yarn kbn bootstrap` to fix the white space that gets generated which would in turn will cause CI failures if left as is.
+
+- Next up we want to determine the version of chromium that's been verified to work with this specific version of puppeteer, we so by run a utility script created solely for this purpose within Kibana; `node scripts/chromium_version.js 23.3.1`, On running the aforementioned script we would get a result very similar to the one below;
+
+ ![image][./chromium_version_command.png]
+
+ The important information to take note of here is the chromium revision and commit value. The revision value is important for selecting the appropriate chromium that has been pre-built by google, whilst the commit version we use to build our variant of headless chromium that gets distributed for linux variants of kibana.
+
+## 2. **Specifying Chromium install version for platform variants**
+
+Kibana provides a verified version of chromium that's guaranteed to work for the version of puppeteer we would be upgrading to. For the Mac and Windows platform we use the pre-built binaries provided by google, whilst for linux we build our own because it doesn't exist and it also allows us to strip down the build to the bare minimum required to run reporting.
+
+For the tasks, ahead the file located at [https://github.com/elastic/kibana/blob/main/packages/kbn-screenshotting-server/src/paths.ts](https://github.com/elastic/kibana/blob/main/packages/kbn-screenshotting-server/src/paths.ts) would require some edits.
+
+Taking a look at the aforementioned file, you'd notice there's a `ChromiumArchivePaths` definition that specifies platform, revision, checksum information. This is how Kibana is informed of the appropriate chromium version to install. This information is sourced from [here](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html), and the process is quite manual.
+
+It's worth pointing out that to avoid errors it's recommended to create a separate directory that all the assets for this upgrade would be accessed from.
+
+### **2.1 Determining and specifying chromium version details for Mac and Windows**
+
+For example to determine the appropriate version for Mac;
+
+- We navigate to the following link; [https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html)
+
+- Select Mac, in the search field we provide the expected revision number for the current puppeteer version, for the purpose of this guide, the revision number to input is `1331488`. It's worth pointing out that it oftentimes happens that the specific revision doesn't exists; As is the case for this specific revision see image below;
+
+ ![image][./browser_snapshots_filter1.png]
+
+ In the event that this is the case, we can opt to delete the last digit from the revision (or whatever heuristics works for you), so we might find an existing revision that's as close as possible to the expected revision number.
+
+ ![image][./browser_snapshots_filter2.png]
+
+ On doing this we are presented with a couple of options, in this case we would opt to pick revision `1331485` as it's the closest to the expected `1331488` revision.
+
+- After deciding on a revision copy this value and update the revision value in paths.ts for the selected package with it's archive path titled ‘Mac', we might then go ahead to open the directory, within the directory we would typically be presented with couple of files like so;
+
+ ![image][./browser_snapshots_listing.png]
+
+ For whichever platform we are performing this task for, we want to download the file that matches the current platform we are updating's `archiveFilename` value, in this case it's titled `chrome-mac.zip`.
+- We then download this file, so we can compute it's checksum, (we use sha256), so that we can verify later on for users that will be downloading this same archive that they got the correct one.
+
+- To compute said checksum we might do so by leveraging pre-installed utils on most unix machines like so; `sha256sum `.
+
+- We'd then want to copy the checksum value of this file and update the current value of `archiveChecksum`, next we extract the content of this archive, so we might also compute the value of `binaryChecksum` to determine the correct path for the platform; we check the value of `binaryRelativePath` knowing this value we run the sha256sum util against this path and update the current value with the one received from the computation.
+
+ We repeat the same process for Windows.
+
+### **2.2 Providing chromium version for Linux**
+
+Given that chromium is typically operated with a GUI and our assumption is that folks that are installing kibana on linux machines are doing so on their servers, hence the variant of chromium that kibana uses on linux machines is slightly different in that by default they are able to run without a GUI for this reason we build our own variant.
+
+However because the chromium codebase is large to say the least, we build the headless chrome on a VM, for the next steps, you'll need to have access to the "elastic-kibana" project on google console. Also have `gcloud` cli tool installed. (You can install this pretty quickly using [brew](https://formulae.brew.sh/cask/google-cloud-sdk), alternatively if you'd rather install from source because reasons, you'd want to [see here](https://cloud.google.com/sdk/docs/install))
+
+ A VM template has been created that fulfills all the compute, network and build requirements so that the build process is sped up as much as possible;
+
+- On GCP, opt to create a VM from an instance template, the template you should select would be the **"rd-build-chromium-linux"** template. On creating this VM, you'd want to take a look at the README file for building chromium **[here](https://github.com/elastic/kibana/tree/main/x-pack/build_chromium)**.
+- Next you'd want to connect to the VM using the gcloud tool we'd initially installed, you can grab the command for your own particular VM by selecting the VM and clicking the SSH option, when you select the view gcloud command. The value displayed to you should be something along the lines of;
+
+ ```
+ gcloud compute ssh --zone "us-central1-a" "rd-eokoneyo-build-chromium-linux" --project "elastic-kibana-184716" --ssh-key-file ~/.ssh/id_ed25519
+ ```
+
+ Depending on the setup you have you might not need to pass your SSH key, but if it's slightly different you might need to pass a key that's identifiable by google using the **"--ssh-key-file"** option.
+
+- From here on simply run through the steps outlined in the readme.
+
+- If it happens that on completing the build your user doesn't have permissions within the VM to upload the build artifacts to the dedicated storage. This is fine, we'll deal with that later.
+- Next we need to extract said artifact from your VM so we can compute the checksums for the archive and the binary we just built.
+
+- To get the build artifact out of the VM, you'd want to leverage the **scp** functionality provided by gcloud cli; We do this by running a command similar to the one below again only providing the ssh key if it is required to identify yourself, we should replace the host name with the name of your VM and the path to the zip file with the correct path on your vm;
+
+ ```
+ gcloud compute scp --zone "us-central1-a" --project "elastic-kibana-184716" --ssh-key-file ~/.ssh/id_ed25519 rd-eokoneyo-build-chromium-linux:/home/eyo.eyo/chromium/chromium/src/out/headless/chromium-fe621c5-locales-linux_x64.zip .
+ ```
+
+ Preferably run this command in the same directory we'd created to contain all our build artifacts. This command assumes we just built the linux x64 variant, there will be two files created: a **.zip** and a **.sha256** file (this is useful to verify that the file we download from the build is not corrupted). We'd want to run a similar command to download the .sha256 file too.
+
+ We should perform this step before building another variant (i.e. the arm64) because artifacts from the previous build gets cleaned out.
+
+- On downloading both the .zip and .sha256 file to our machine, we attempt to calculate the checksum of the just downloaded archive; it should equal the value of the contents of the .sha256 file. Assuming all is well we would repeat the same process we did undertake for the mac and windows platform updating path.ts with the checksum value. In every update the value of revision for linux is always the version our script provided.
+
+- Assuming we've completed this step for both variants we currently build for; we might then choose to upload the archives and their respective .sha256 files to the storage buckets so they can be accessed publicly, leveraging gcloud cli this time with the gsutil command that comes bundled with gcloud;
+
+ Running the following command;
+
+ ```
+ gsutil cp ./chromium-fe621c5-locales-linux_* gs://headless_shell_staging
+ ```
+
+ and
+
+ ```
+ gsutil cp ./chromium-fe621c5-locales-linux_* gs://headless_shell
+ ```
+
+ Would copy the files to both the staging and production buckets.
+
+If you've made it all the way through, all that's left is testing everything works as it should.
+
+## 3. Testing
+
+### **3.1 Locally**
+
+The first step of verification is to start kibana, locally. On whatever machine you use kibana should be able to download the new version of chromium from the updated path.ts file, it might help to add the following to your kibana.yml;
+
+```
+ logging.loggers:
+ - name: plugins.reporting
+ level: debug
+ - name: plugins.screenshotting
+ level: debug
+```
+so we can verify that on requesting report generation the chromium version being used is indeed the one we just provided;
+
+### **3.2 Docker**
+
+This step is required to validate our linux builds, especially considering that we are mostly developing with machines that have a GUI, For this step you want to follow the instructions outlined in an example puppeteer update PR [https://github.com/elastic/kibana/pull/192345](https://github.com/elastic/kibana/pull/192345)
+
+### 3.3 **CI**
+
+CI also runs against the build too pulling the assets we upload to the buckets, to run integration test.
diff --git a/package.json b/package.json
index 0a7c0d6936d0a..2fc8ba6e22aef 100644
--- a/package.json
+++ b/package.json
@@ -1632,7 +1632,7 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-syntax-highlighter": "^15.4.0",
"@types/react-test-renderer": "^17.0.2",
- "@types/react-virtualized": "^9.21.22",
+ "@types/react-virtualized": "^9.21.30",
"@types/react-window": "^1.8.8",
"@types/react-window-infinite-loader": "^1.0.9",
"@types/redux-actions": "^2.6.1",
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/columns.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/columns.test.ts
index e79c418eeb3cc..38e98104d41bd 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/columns.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/columns.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('Column Identifier Expressions', () => {
it('can parse un-quoted identifiers', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts
index 30d44d447387e..6fb176c9624f7 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/commands.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('commands', () => {
describe('correctly formatted, basic usage', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/from.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/from.test.ts
index 101661973a692..f2f0fded57ca5 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/from.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/from.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('FROM', () => {
describe('correctly formatted', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts
index 8ec533816a56e..9d822f78f9333 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/function.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
import { Walker } from '../../walker';
describe('function AST nodes', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/inlinecast.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/inlinecast.test.ts
index d0650ab3f3213..889ca2a2ecf3d 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/inlinecast.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/inlinecast.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
import { ESQLFunction, ESQLInlineCast, ESQLSingleAstItem } from '../../types';
describe('Inline cast (::)', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/literal.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/literal.test.ts
index 514d769d5c45e..7f50198c96047 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/literal.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/literal.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
import { ESQLLiteral } from '../../types';
describe('literal expression', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/metrics.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/metrics.test.ts
index 54ddc49c5d048..d33c94e8903ac 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/metrics.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/metrics.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('METRICS', () => {
describe('correctly formatted', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/params.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/params.test.ts
index 8586236eeb2f9..e4b1a892d32d9 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/params.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/params.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
import { Walker } from '../../walker';
/**
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/rename.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/rename.test.ts
index 4acad891150b2..214e5c1d36882 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/rename.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/rename.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('RENAME', () => {
/**
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/sort.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/sort.test.ts
index cfaec0a6e39e9..981eac40b68ae 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/sort.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/sort.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('SORT', () => {
describe('correctly formatted', () => {
diff --git a/packages/kbn-esql-ast/src/parser/__tests__/where.test.ts b/packages/kbn-esql-ast/src/parser/__tests__/where.test.ts
index d507b559fd407..f3f6aeb886ec0 100644
--- a/packages/kbn-esql-ast/src/parser/__tests__/where.test.ts
+++ b/packages/kbn-esql-ast/src/parser/__tests__/where.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getAstAndSyntaxErrors as parse } from '..';
+import { parse } from '..';
describe('WHERE', () => {
describe('correctly formatted', () => {
diff --git a/packages/kbn-esql-ast/src/parser/parser.ts b/packages/kbn-esql-ast/src/parser/parser.ts
index ad263a49ebd00..f99e00e92d1e0 100644
--- a/packages/kbn-esql-ast/src/parser/parser.ts
+++ b/packages/kbn-esql-ast/src/parser/parser.ts
@@ -100,33 +100,66 @@ export interface ParseResult {
}
export const parse = (text: string | undefined, options: ParseOptions = {}): ParseResult => {
- if (text == null) {
- const commands: ESQLAstQueryExpression['commands'] = [];
- return { ast: commands, root: Builder.expression.query(commands), errors: [], tokens: [] };
+ try {
+ if (text == null) {
+ const commands: ESQLAstQueryExpression['commands'] = [];
+ return { ast: commands, root: Builder.expression.query(commands), errors: [], tokens: [] };
+ }
+ const errorListener = new ESQLErrorListener();
+ const parseListener = new ESQLAstBuilderListener();
+ const { tokens, parser } = getParser(
+ CharStreams.fromString(text),
+ errorListener,
+ parseListener
+ );
+
+ parser[GRAMMAR_ROOT_RULE]();
+
+ const errors = errorListener.getErrors().filter((error) => {
+ return !SYNTAX_ERRORS_TO_IGNORE.includes(error.message);
+ });
+ const { ast: commands } = parseListener.getAst();
+ const root = Builder.expression.query(commands, {
+ location: {
+ min: 0,
+ max: text.length - 1,
+ },
+ });
+
+ if (options.withFormatting) {
+ const decorations = collectDecorations(tokens);
+ attachDecorations(root, tokens.tokens, decorations.lines);
+ }
+
+ return { root, ast: commands, errors, tokens: tokens.tokens };
+ } catch (error) {
+ /**
+ * Parsing should never fail, meaning this branch should never execute. But
+ * if it does fail, we want to log the error message for easier debugging.
+ */
+ // eslint-disable-next-line no-console
+ console.error(error);
+
+ const root = Builder.expression.query();
+
+ return {
+ root,
+ ast: root.commands,
+ errors: [
+ {
+ startLineNumber: 0,
+ endLineNumber: 0,
+ startColumn: 0,
+ endColumn: 0,
+ message:
+ 'Parsing internal error: ' +
+ (!!error && typeof error === 'object' ? String(error.message) : String(error)),
+ severity: 'error',
+ },
+ ],
+ tokens: [],
+ };
}
- const errorListener = new ESQLErrorListener();
- const parseListener = new ESQLAstBuilderListener();
- const { tokens, parser } = getParser(CharStreams.fromString(text), errorListener, parseListener);
-
- parser[GRAMMAR_ROOT_RULE]();
-
- const errors = errorListener.getErrors().filter((error) => {
- return !SYNTAX_ERRORS_TO_IGNORE.includes(error.message);
- });
- const { ast: commands } = parseListener.getAst();
- const root = Builder.expression.query(commands, {
- location: {
- min: 0,
- max: text.length - 1,
- },
- });
-
- if (options.withFormatting) {
- const decorations = collectDecorations(tokens);
- attachDecorations(root, tokens.tokens, decorations.lines);
- }
-
- return { root, ast: commands, errors, tokens: tokens.tokens };
};
export const parseErrors = (text: string) => {
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
index 8524cd4950955..e463902554074 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
@@ -1262,41 +1262,33 @@ describe('autocomplete', () => {
describe('Replacement ranges are attached when needed', () => {
testSuggestions('FROM a | WHERE doubleField IS NOT N/', [
{ text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 35 } },
- { text: 'IS NULL', rangeToReplace: { start: 35, end: 35 } },
+ { text: 'IS NULL', rangeToReplace: { start: 36, end: 36 } },
'!= $0',
- '< $0',
- '<= $0',
'== $0',
- '> $0',
- '>= $0',
'IN $0',
+ 'AND $0',
+ 'NOT',
+ 'OR $0',
]);
testSuggestions('FROM a | WHERE doubleField IS N/', [
{ text: 'IS NOT NULL', rangeToReplace: { start: 28, end: 31 } },
{ text: 'IS NULL', rangeToReplace: { start: 28, end: 31 } },
- { text: '!= $0', rangeToReplace: { start: 31, end: 31 } },
- '< $0',
- '<= $0',
+ { text: '!= $0', rangeToReplace: { start: 32, end: 32 } },
'== $0',
- '> $0',
- '>= $0',
'IN $0',
+ 'AND $0',
+ 'NOT',
+ 'OR $0',
]);
testSuggestions('FROM a | EVAL doubleField IS NOT N/', [
{ text: 'IS NOT NULL', rangeToReplace: { start: 27, end: 34 } },
'IS NULL',
- '% $0',
- '* $0',
- '+ $0',
- '- $0',
- '/ $0',
'!= $0',
- '< $0',
- '<= $0',
'== $0',
- '> $0',
- '>= $0',
'IN $0',
+ 'AND $0',
+ 'NOT',
+ 'OR $0',
]);
describe('dot-separated field names', () => {
testSuggestions(
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
index 5d885379f1a94..cf41fd2506c72 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
@@ -324,10 +324,16 @@ function findNewVariable(variables: Map) {
function workoutBuiltinOptions(
nodeArg: ESQLAstItem,
references: Pick
-): { skipAssign: boolean } {
+): { skipAssign: boolean; commandsToInclude?: string[] } {
+ const commandsToInclude =
+ (isSingleItem(nodeArg) && nodeArg.text?.toLowerCase().trim().endsWith('null')) ?? false
+ ? ['and', 'or']
+ : undefined;
+
// skip assign operator if it's a function or an existing field to avoid promoting shadowing
return {
skipAssign: Boolean(!isColumnItem(nodeArg) || getColumnForASTNode(nodeArg, references)),
+ commandsToInclude,
};
}
@@ -447,7 +453,10 @@ function isFunctionArgComplete(
}
const hasCorrectTypes = fnDefinition.signatures.some((def) => {
return arg.args.every((a, index) => {
- return def.params[index].type === extractTypeFromASTArg(a, references);
+ return (
+ (fnDefinition.name.endsWith('null') && def.params[index].type === 'any') ||
+ def.params[index].type === extractTypeFromASTArg(a, references)
+ );
});
});
if (!hasCorrectTypes) {
@@ -1140,11 +1149,12 @@ async function getBuiltinFunctionNextArgument(
}
return suggestions.map((s) => {
const overlap = getOverlapRange(queryText, s.text);
+ const offset = overlap.start === overlap.end ? 1 : 0;
return {
...s,
rangeToReplace: {
- start: overlap.start,
- end: overlap.end,
+ start: overlap.start + offset,
+ end: overlap.end + offset,
},
};
});
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts
index 42bb02058023b..8d598eb5f2f11 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts
@@ -59,10 +59,11 @@ export const getBuiltinCompatibleFunctionDefinition = (
option: string | undefined,
argType: FunctionParameterType,
returnTypes?: FunctionReturnType[],
- { skipAssign }: { skipAssign?: boolean } = {}
+ { skipAssign, commandsToInclude }: { skipAssign?: boolean; commandsToInclude?: string[] } = {}
): SuggestionRawDefinition[] => {
const compatibleFunctions = [...builtinFunctions, ...getTestFunctions()].filter(
({ name, supportedCommands, supportedOptions, signatures, ignoreAsSuggestion }) =>
+ (command === 'where' && commandsToInclude ? commandsToInclude.indexOf(name) > -1 : true) &&
!ignoreAsSuggestion &&
!/not_/.test(name) &&
(!skipAssign || name !== '=') &&
diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
index ce9cec58575fc..e3e3da4277344 100644
--- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts
@@ -46,7 +46,7 @@ import {
} from '../definitions/types';
import type { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types';
import { removeMarkerArgFromArgsList } from './context';
-import { isNumericDecimalType } from './esql_types';
+import { compareTypesWithLiterals, isNumericDecimalType } from './esql_types';
import type { ReasonTypes } from './types';
import { DOUBLE_TICKS_REGEX, EDITOR_MARKER, SINGLE_BACKTICK } from './constants';
import type { EditorContext } from '../autocomplete/types';
@@ -473,7 +473,7 @@ export function checkFunctionArgMatchesDefinition(
const lowerArgType = argType?.toLowerCase();
const lowerArgCastType = arg.castType?.toLowerCase();
return (
- lowerArgType === lowerArgCastType ||
+ compareTypesWithLiterals(lowerArgCastType, lowerArgType) ||
// for valid shorthand casts like 321.12::int or "false"::bool
(['int', 'bool'].includes(lowerArgCastType) && argType.startsWith(lowerArgCastType))
);
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
index 736159b36384d..a646c0323a76f 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json
@@ -9546,6 +9546,11 @@
"error": [],
"warning": []
},
+ {
+ "query": "from a_index | where 1::string==\"keyword\"",
+ "error": [],
+ "warning": []
+ },
{
"query": "from a_index | eval trim(\"23\"::double)",
"error": [
diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
index 66de6c7fc70ad..dd04f0e506fe8 100644
--- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts
@@ -1636,6 +1636,8 @@ describe('validation logic', () => {
// accepts casting with multiple types
testErrorsAndWarnings('from a_index | eval 1::keyword::long::double', []);
+ testErrorsAndWarnings('from a_index | where 1::string=="keyword"', []);
+
// takes into account casting in function arguments
testErrorsAndWarnings('from a_index | eval trim("23"::double)', [
'Argument of [trim] must be [keyword], found value ["23"::double] type [double]',
diff --git a/test/functional/apps/discover/group2_data_grid1/_data_grid_context.ts b/test/functional/apps/discover/group2_data_grid1/_data_grid_context.ts
index 0757304199d8a..6a7a3fc80343e 100644
--- a/test/functional/apps/discover/group2_data_grid1/_data_grid_context.ts
+++ b/test/functional/apps/discover/group2_data_grid1/_data_grid_context.ts
@@ -40,7 +40,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const security = getService('security');
- describe('discover data grid context tests', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196120
+ describe.skip('discover data grid context tests', () => {
before(async () => {
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] });
diff --git a/x-pack/build_chromium/README.md b/x-pack/build_chromium/README.md
index 5f9e2bb06317d..15d335077f7f0 100644
--- a/x-pack/build_chromium/README.md
+++ b/x-pack/build_chromium/README.md
@@ -83,22 +83,6 @@ settings, including the defaults. Some build flags are documented
**NOTE:** Please, make sure you consult @elastic/kibana-security before you change, remove or add any of the build flags.
-## Directions for Elasticians
-
-If you wish to use a remote VM to build, you'll need access to our GCP account.
-
-**NOTE:** The builds should be done in Ubuntu on x64 architecture. ARM builds
-are created in x64 using cross-compiling. CentOS is not supported for building Chromium.
-
-1. Login to Google Cloud Console
-2. Click the "Compute Engine" tab.
-3. Create a Linux VM:
- - 8 CPU
- - 30GB memory
- - 80GB free space on disk (Try `ncdu /home` to see where space is used.)
- - "Cloud API access scopes": must have **read / write** scope for the Storage API. Access scopes in the GCP VM instance needs to be set to allow full access to all Cloud APIs vs default access (this will return a 403 otherwise in the build.py script)
-4. Install [Google Cloud SDK](https://cloud.google.com/sdk) locally to ssh into the GCP instance
-
## Artifacts
After the build completes, there will be a .zip file and a .md5 file in `~/chromium/chromium/src/out/headless`. These are named like so: `chromium-{first_7_of_SHA}-{platform}-{arch}`, for example: `chromium-4747cc2-linux-x64`.
diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx
index c3989f6971fff..5b80a34e0bf7b 100644
--- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx
+++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx
@@ -45,6 +45,7 @@ import { SimulatedFunctionCallingCallout } from './simulated_function_calling_ca
import { WelcomeMessage } from './welcome_message';
import { useLicense } from '../hooks/use_license';
import { PromptEditor } from '../prompt_editor/prompt_editor';
+import { deserializeMessage } from '../utils/deserialize_message';
const fullHeightClassName = css`
height: 100%;
@@ -226,9 +227,11 @@ export function ChatBody({
});
const handleCopyConversation = () => {
+ const deserializedMessages = (conversation.value?.messages ?? messages).map(deserializeMessage);
+
const content = JSON.stringify({
title: initialTitle,
- messages: conversation.value?.messages ?? messages,
+ messages: deserializedMessages,
});
navigator.clipboard?.writeText(content || '');
diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.test.ts b/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.test.ts
new file mode 100644
index 0000000000000..b2c067a3e9f10
--- /dev/null
+++ b/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.test.ts
@@ -0,0 +1,118 @@
+/*
+ * 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 { cloneDeep } from 'lodash';
+import { Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/common';
+import { deserializeMessage } from './deserialize_message';
+import { safeJsonParse } from './safe_json_parse';
+
+jest.mock('lodash', () => ({
+ cloneDeep: jest.fn(),
+}));
+
+jest.mock('./safe_json_parse', () => ({
+ safeJsonParse: jest.fn((value) => {
+ try {
+ return JSON.parse(value);
+ } catch {
+ return value;
+ }
+ }),
+}));
+
+describe('deserializeMessage', () => {
+ const baseMessage: Message = {
+ '@timestamp': '2024-10-15T00:00:00Z',
+ message: {
+ role: MessageRole.User,
+ content: 'This is a message',
+ },
+ };
+
+ beforeEach(() => {
+ (cloneDeep as jest.Mock).mockImplementation((obj) => JSON.parse(JSON.stringify(obj)));
+ });
+
+ it('should clone the original message', () => {
+ const message = { ...baseMessage };
+ deserializeMessage(message);
+
+ expect(cloneDeep).toHaveBeenCalledWith(message);
+ });
+
+ it('should deserialize function_call.arguments if it is a string', () => {
+ const messageWithFunctionCall: Message = {
+ ...baseMessage,
+ message: {
+ ...baseMessage.message,
+ function_call: {
+ name: 'testFunction',
+ arguments: '{"key": "value"}',
+ trigger: MessageRole.Assistant,
+ },
+ },
+ };
+
+ const result = deserializeMessage(messageWithFunctionCall);
+
+ expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
+ expect(result.message.function_call!.arguments).toEqual({ key: 'value' });
+ });
+
+ it('should deserialize message.content if it is a string', () => {
+ const messageWithContent: Message = {
+ ...baseMessage,
+ message: {
+ ...baseMessage.message,
+ name: 'testMessage',
+ content: '{"key": "value"}',
+ },
+ };
+
+ const result = deserializeMessage(messageWithContent);
+
+ expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
+ expect(result.message.content).toEqual({ key: 'value' });
+ });
+
+ it('should deserialize message.data if it is a string', () => {
+ const messageWithData: Message = {
+ ...baseMessage,
+ message: {
+ ...baseMessage.message,
+ name: 'testMessage',
+ data: '{"key": "value"}',
+ },
+ };
+
+ const result = deserializeMessage(messageWithData);
+
+ expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
+ expect(result.message.data).toEqual({ key: 'value' });
+ });
+
+ it('should return the copied message as is if no deserialization is needed', () => {
+ const messageWithoutSerialization: Message = {
+ ...baseMessage,
+ message: {
+ ...baseMessage.message,
+ function_call: {
+ name: 'testFunction',
+ arguments: '',
+ trigger: MessageRole.Assistant,
+ },
+ content: '',
+ },
+ };
+
+ const result = deserializeMessage(messageWithoutSerialization);
+
+ expect(result.message.function_call!.name).toEqual('testFunction');
+ expect(result.message.function_call!.arguments).toEqual('');
+ expect(result.message.content).toEqual('');
+ });
+});
diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.ts b/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.ts
new file mode 100644
index 0000000000000..445e6330981a9
--- /dev/null
+++ b/x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { cloneDeep } from 'lodash';
+import type { Message } from '@kbn/observability-ai-assistant-plugin/common';
+import { safeJsonParse } from './safe_json_parse';
+
+export const deserializeMessage = (message: Message): Message => {
+ const copiedMessage = cloneDeep(message);
+
+ if (
+ copiedMessage.message.function_call?.arguments &&
+ typeof copiedMessage.message.function_call?.arguments === 'string'
+ ) {
+ copiedMessage.message.function_call.arguments = safeJsonParse(
+ copiedMessage.message.function_call.arguments ?? '{}'
+ );
+ }
+
+ if (copiedMessage.message.name) {
+ if (copiedMessage.message.content && typeof copiedMessage.message.content === 'string') {
+ copiedMessage.message.content = safeJsonParse(copiedMessage.message.content);
+ }
+
+ if (copiedMessage.message.data && typeof copiedMessage.message.data === 'string') {
+ copiedMessage.message.data = safeJsonParse(copiedMessage.message.data);
+ }
+ }
+
+ return copiedMessage;
+};
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx
index 3b48c8d0861c5..57ad1312a271e 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/alerts_settings/alerts_settings.tsx
@@ -13,9 +13,9 @@ import { KnowledgeBaseConfig } from '../../types';
import { AlertsRange } from '../../../knowledge_base/alerts_range';
import * as i18n from '../../../knowledge_base/translations';
-export const MIN_LATEST_ALERTS = 10;
-export const MAX_LATEST_ALERTS = 100;
-export const TICK_INTERVAL = 10;
+export const MIN_LATEST_ALERTS = 50;
+export const MAX_LATEST_ALERTS = 500;
+export const TICK_INTERVAL = 50;
export const RANGE_CONTAINER_WIDTH = 600; // px
const LABEL_WRAPPER_MIN_WIDTH = 95; // px
diff --git a/x-pack/plugins/actions/common/routes/connector/apis/execute/index.ts b/x-pack/plugins/actions/common/routes/connector/apis/execute/index.ts
new file mode 100644
index 0000000000000..448428839336d
--- /dev/null
+++ b/x-pack/plugins/actions/common/routes/connector/apis/execute/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export {
+ executeConnectorRequestParamsSchema,
+ executeConnectorRequestBodySchema,
+} from './schemas/latest';
+export type { ExecuteConnectorRequestParams, ExecuteConnectorRequestBody } from './types/latest';
+
+export {
+ executeConnectorRequestParamsSchema as executeConnectorRequestParamsSchemaV1,
+ executeConnectorRequestBodySchema as executeConnectorRequestBodySchemaV1,
+} from './schemas/v1';
+export type {
+ ExecuteConnectorRequestParams as ExecuteConnectorRequestParamsV1,
+ ExecuteConnectorRequestBody as ExecuteConnectorRequestBodyV1,
+} from './types/v1';
diff --git a/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/latest.ts b/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/latest.ts
new file mode 100644
index 0000000000000..25300c97a6d2e
--- /dev/null
+++ b/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/latest.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './v1';
diff --git a/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/v1.ts b/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/v1.ts
new file mode 100644
index 0000000000000..1f41763a004a2
--- /dev/null
+++ b/x-pack/plugins/actions/common/routes/connector/apis/execute/schemas/v1.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+
+export const executeConnectorRequestParamsSchema = schema.object({
+ id: schema.string({
+ meta: {
+ description: 'An identifier for the connector.',
+ },
+ }),
+});
+
+export const executeConnectorRequestBodySchema = schema.object({
+ params: schema.recordOf(schema.string(), schema.any()),
+});
diff --git a/x-pack/plugins/actions/common/routes/connector/apis/execute/types/latest.ts b/x-pack/plugins/actions/common/routes/connector/apis/execute/types/latest.ts
new file mode 100644
index 0000000000000..25300c97a6d2e
--- /dev/null
+++ b/x-pack/plugins/actions/common/routes/connector/apis/execute/types/latest.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './v1';
diff --git a/x-pack/plugins/actions/common/routes/connector/apis/execute/types/v1.ts b/x-pack/plugins/actions/common/routes/connector/apis/execute/types/v1.ts
new file mode 100644
index 0000000000000..cc1b6e4cdc196
--- /dev/null
+++ b/x-pack/plugins/actions/common/routes/connector/apis/execute/types/v1.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { TypeOf } from '@kbn/config-schema';
+import { executeConnectorRequestParamsSchemaV1, executeConnectorRequestBodySchemaV1 } from '..';
+
+export type ExecuteConnectorRequestParams = TypeOf;
+export type ExecuteConnectorRequestBody = TypeOf;
diff --git a/x-pack/plugins/actions/common/routes/connector/response/index.ts b/x-pack/plugins/actions/common/routes/connector/response/index.ts
index c870698329052..3a58325a542ed 100644
--- a/x-pack/plugins/actions/common/routes/connector/response/index.ts
+++ b/x-pack/plugins/actions/common/routes/connector/response/index.ts
@@ -6,11 +6,16 @@
*/
// Latest
-export type { ConnectorResponse, AllConnectorsResponse } from './types/latest';
+export type {
+ ConnectorResponse,
+ AllConnectorsResponse,
+ ConnectorExecuteResponse,
+} from './types/latest';
export {
connectorResponseSchema,
allConnectorsResponseSchema,
connectorTypesResponseSchema,
+ connectorExecuteResponseSchema,
} from './schemas/latest';
// v1
@@ -18,9 +23,11 @@ export type {
ConnectorResponse as ConnectorResponseV1,
AllConnectorsResponse as AllConnectorsResponseV1,
ConnectorTypesResponse as ConnectorTypesResponseV1,
+ ConnectorExecuteResponse as ConnectorExecuteResponseV1,
} from './types/v1';
export {
connectorResponseSchema as connectorResponseSchemaV1,
allConnectorsResponseSchema as connectorWithExtraFindDataSchemaV1,
connectorTypesResponseSchema as connectorTypesResponseSchemaV1,
+ connectorExecuteResponseSchema as connectorExecuteResponseSchemaV1,
} from './schemas/v1';
diff --git a/x-pack/plugins/actions/common/routes/connector/response/schemas/latest.ts b/x-pack/plugins/actions/common/routes/connector/response/schemas/latest.ts
index bc4edc5be46d0..c89efd04bc485 100644
--- a/x-pack/plugins/actions/common/routes/connector/response/schemas/latest.ts
+++ b/x-pack/plugins/actions/common/routes/connector/response/schemas/latest.ts
@@ -8,3 +8,4 @@
export { connectorResponseSchema } from './v1';
export { allConnectorsResponseSchema } from './v1';
export { connectorTypesResponseSchema } from './v1';
+export { connectorExecuteResponseSchema } from './v1';
diff --git a/x-pack/plugins/actions/common/routes/connector/response/schemas/v1.ts b/x-pack/plugins/actions/common/routes/connector/response/schemas/v1.ts
index 5c9b95ca8fc7b..096e2f2943d80 100644
--- a/x-pack/plugins/actions/common/routes/connector/response/schemas/v1.ts
+++ b/x-pack/plugins/actions/common/routes/connector/response/schemas/v1.ts
@@ -98,3 +98,55 @@ export const connectorTypesResponseSchema = schema.object({
meta: { description: 'Indicates whether the action is a system action.' },
}),
});
+
+export const connectorExecuteResponseSchema = schema.object({
+ connector_id: schema.string({
+ meta: {
+ description: 'The identifier for the connector.',
+ },
+ }),
+ status: schema.oneOf([schema.literal('ok'), schema.literal('error')], {
+ meta: {
+ description: 'The outcome of the connector execution.',
+ },
+ }),
+ message: schema.maybe(
+ schema.string({
+ meta: {
+ description: 'The connector execution error message.',
+ },
+ })
+ ),
+ service_message: schema.maybe(
+ schema.string({
+ meta: {
+ description: 'An error message that contains additional details.',
+ },
+ })
+ ),
+ data: schema.maybe(
+ schema.any({
+ meta: {
+ description: 'The connector execution data.',
+ },
+ })
+ ),
+ retry: schema.maybe(
+ schema.nullable(
+ schema.oneOf([schema.boolean(), schema.string()], {
+ meta: {
+ description:
+ 'When the status is error, identifies whether the connector execution will retry .',
+ },
+ })
+ )
+ ),
+ errorSource: schema.maybe(
+ schema.oneOf([schema.literal('user'), schema.literal('framework')], {
+ meta: {
+ description:
+ 'When the status is error, identifies whether the error is a framework error or a user error.',
+ },
+ })
+ ),
+});
diff --git a/x-pack/plugins/actions/common/routes/connector/response/types/v1.ts b/x-pack/plugins/actions/common/routes/connector/response/types/v1.ts
index 3bf7401d2d0e0..499cc2ec21d48 100644
--- a/x-pack/plugins/actions/common/routes/connector/response/types/v1.ts
+++ b/x-pack/plugins/actions/common/routes/connector/response/types/v1.ts
@@ -10,6 +10,7 @@ import {
connectorResponseSchemaV1,
connectorTypesResponseSchemaV1,
allConnectorsResponseSchema,
+ connectorExecuteResponseSchema,
} from '..';
type ConnectorResponseSchemaType = TypeOf;
@@ -41,3 +42,14 @@ export interface ConnectorTypesResponse {
supported_feature_ids: ConnectorTypesResponseSchemaType['supported_feature_ids'];
is_system_action_type: ConnectorTypesResponseSchemaType['is_system_action_type'];
}
+
+type ConnectorExecuteResponseSchemaType = TypeOf;
+export interface ConnectorExecuteResponse {
+ connector_id: ConnectorExecuteResponseSchemaType['connector_id'];
+ status: ConnectorExecuteResponseSchemaType['status'];
+ message?: ConnectorExecuteResponseSchemaType['message'];
+ service_message?: ConnectorExecuteResponseSchemaType['service_message'];
+ data?: ConnectorExecuteResponseSchemaType['data'];
+ retry?: ConnectorExecuteResponseSchemaType['retry'];
+ errorSource?: ConnectorExecuteResponseSchemaType['errorSource'];
+}
diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.ts b/x-pack/plugins/actions/server/actions_client/actions_client.ts
index f485d82b2f120..edad072acbca6 100644
--- a/x-pack/plugins/actions/server/actions_client/actions_client.ts
+++ b/x-pack/plugins/actions/server/actions_client/actions_client.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { v4 as uuidv4 } from 'uuid';
import Boom from '@hapi/boom';
import url from 'url';
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
@@ -30,6 +29,7 @@ import { get } from '../application/connector/methods/get';
import { getAll, getAllSystemConnectors } from '../application/connector/methods/get_all';
import { update } from '../application/connector/methods/update';
import { listTypes } from '../application/connector/methods/list_types';
+import { execute } from '../application/connector/methods/execute';
import {
GetGlobalExecutionKPIParams,
GetGlobalExecutionLogParams,
@@ -54,7 +54,6 @@ import {
HookServices,
} from '../types';
import { PreconfiguredActionDisabledModificationError } from '../lib/errors/preconfigured_action_disabled_modification';
-import { ExecuteOptions } from '../lib/action_executor';
import {
ExecutionEnqueuer,
ExecuteOptions as EnqueueExecutionOptions,
@@ -96,6 +95,9 @@ import { connectorFromSavedObject, isConnectorDeprecated } from '../application/
import { ListTypesParams } from '../application/connector/methods/list_types/types';
import { ConnectorUpdateParams } from '../application/connector/methods/update/types';
import { ConnectorUpdate } from '../application/connector/methods/update/types/types';
+import { isPreconfigured } from '../lib/is_preconfigured';
+import { isSystemAction } from '../lib/is_system_action';
+import { ConnectorExecuteParams } from '../application/connector/methods/execute/types';
interface Action extends ConnectorUpdate {
actionTypeId: string;
@@ -649,75 +651,10 @@ export class ActionsClient {
return result;
}
- private getSystemActionKibanaPrivileges(connectorId: string, params?: ExecuteOptions['params']) {
- const inMemoryConnector = this.context.inMemoryConnectors.find(
- (connector) => connector.id === connectorId
- );
-
- const additionalPrivileges = inMemoryConnector?.isSystemAction
- ? this.context.actionTypeRegistry.getSystemActionKibanaPrivileges(
- inMemoryConnector.actionTypeId,
- params
- )
- : [];
-
- return additionalPrivileges;
- }
-
- public async execute({
- actionId,
- params,
- source,
- relatedSavedObjects,
- }: Omit): Promise<
- ActionTypeExecutorResult
- > {
- const log = this.context.logger;
-
- if (
- (await getAuthorizationModeBySource(this.context.unsecuredSavedObjectsClient, source)) ===
- AuthorizationMode.RBAC
- ) {
- const additionalPrivileges = this.getSystemActionKibanaPrivileges(actionId, params);
- let actionTypeId: string | undefined;
-
- try {
- if (this.isPreconfigured(actionId) || this.isSystemAction(actionId)) {
- const connector = this.context.inMemoryConnectors.find(
- (inMemoryConnector) => inMemoryConnector.id === actionId
- );
-
- actionTypeId = connector?.actionTypeId;
- } else {
- // TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
- const { attributes } = await this.context.unsecuredSavedObjectsClient.get(
- 'action',
- actionId
- );
-
- actionTypeId = attributes.actionTypeId;
- }
- } catch (err) {
- log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
- }
-
- await this.context.authorization.ensureAuthorized({
- operation: 'execute',
- additionalPrivileges,
- actionTypeId,
- });
- } else {
- trackLegacyRBACExemption('execute', this.context.usageCounter);
- }
-
- return this.context.actionExecutor.execute({
- actionId,
- params,
- source,
- request: this.context.request,
- relatedSavedObjects,
- actionExecutionId: uuidv4(),
- });
+ public async execute(
+ connectorExecuteParams: ConnectorExecuteParams
+ ): Promise> {
+ return execute(this.context, connectorExecuteParams);
}
public async bulkEnqueueExecution(
@@ -789,15 +726,11 @@ export class ActionsClient {
}
public isPreconfigured(connectorId: string): boolean {
- return !!this.context.inMemoryConnectors.find(
- (connector) => connector.isPreconfigured && connector.id === connectorId
- );
+ return isPreconfigured(this.context, connectorId);
}
public isSystemAction(connectorId: string): boolean {
- return !!this.context.inMemoryConnectors.find(
- (connector) => connector.isSystemAction && connector.id === connectorId
- );
+ return isSystemAction(this.context, connectorId);
}
public async getGlobalExecutionLogWithAuth({
diff --git a/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts b/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts
new file mode 100644
index 0000000000000..f9922e0b61a8d
--- /dev/null
+++ b/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts
@@ -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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { v4 as uuidv4 } from 'uuid';
+import { RawAction, ActionTypeExecutorResult } from '../../../../types';
+import { getSystemActionKibanaPrivileges } from '../../../../lib/get_system_action_kibana_privileges';
+import { isPreconfigured } from '../../../../lib/is_preconfigured';
+import { isSystemAction } from '../../../../lib/is_system_action';
+import {
+ getAuthorizationModeBySource,
+ AuthorizationMode,
+} from '../../../../authorization/get_authorization_mode_by_source';
+import { trackLegacyRBACExemption } from '../../../../lib/track_legacy_rbac_exemption';
+import { ConnectorExecuteParams } from './types';
+import { ACTION_SAVED_OBJECT_TYPE } from '../../../../constants/saved_objects';
+import { ActionsClientContext } from '../../../../actions_client';
+
+export async function execute(
+ context: ActionsClientContext,
+ connectorExecuteParams: ConnectorExecuteParams
+): Promise> {
+ const log = context.logger;
+ const { actionId, params, source, relatedSavedObjects } = connectorExecuteParams;
+
+ if (
+ (await getAuthorizationModeBySource(context.unsecuredSavedObjectsClient, source)) ===
+ AuthorizationMode.RBAC
+ ) {
+ const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params);
+ let actionTypeId: string | undefined;
+
+ try {
+ if (isPreconfigured(context, actionId) || isSystemAction(context, actionId)) {
+ const connector = context.inMemoryConnectors.find(
+ (inMemoryConnector) => inMemoryConnector.id === actionId
+ );
+
+ actionTypeId = connector?.actionTypeId;
+ } else {
+ // TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
+ const { attributes } = await context.unsecuredSavedObjectsClient.get(
+ ACTION_SAVED_OBJECT_TYPE,
+ actionId
+ );
+
+ actionTypeId = attributes.actionTypeId;
+ }
+ } catch (err) {
+ log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
+ }
+
+ await context.authorization.ensureAuthorized({
+ operation: 'execute',
+ additionalPrivileges,
+ actionTypeId,
+ });
+ } else {
+ trackLegacyRBACExemption('execute', context.usageCounter);
+ }
+
+ return context.actionExecutor.execute({
+ actionId,
+ params,
+ source,
+ request: context.request,
+ relatedSavedObjects,
+ actionExecutionId: uuidv4(),
+ });
+}
diff --git a/x-pack/plugins/actions/server/application/connector/methods/execute/index.ts b/x-pack/plugins/actions/server/application/connector/methods/execute/index.ts
new file mode 100644
index 0000000000000..21598e68a047c
--- /dev/null
+++ b/x-pack/plugins/actions/server/application/connector/methods/execute/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { execute } from './execute';
diff --git a/x-pack/plugins/actions/server/application/connector/methods/execute/types/index.ts b/x-pack/plugins/actions/server/application/connector/methods/execute/types/index.ts
new file mode 100644
index 0000000000000..ff2bc6be97a80
--- /dev/null
+++ b/x-pack/plugins/actions/server/application/connector/methods/execute/types/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export type { ConnectorExecuteParams } from './types';
diff --git a/x-pack/plugins/actions/server/application/connector/methods/execute/types/types.ts b/x-pack/plugins/actions/server/application/connector/methods/execute/types/types.ts
new file mode 100644
index 0000000000000..22aa019de599f
--- /dev/null
+++ b/x-pack/plugins/actions/server/application/connector/methods/execute/types/types.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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ExecuteOptions } from '../../../../../lib/action_executor';
+
+export type ConnectorExecuteParams = Omit;
diff --git a/x-pack/plugins/actions/server/lib/get_system_action_kibana_privileges.ts b/x-pack/plugins/actions/server/lib/get_system_action_kibana_privileges.ts
new file mode 100644
index 0000000000000..ef3b8ff853d17
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/get_system_action_kibana_privileges.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { ActionsClientContext } from '../actions_client';
+import { ExecuteOptions } from './action_executor';
+
+export function getSystemActionKibanaPrivileges(
+ context: ActionsClientContext,
+ connectorId: string,
+ params?: ExecuteOptions['params']
+) {
+ const inMemoryConnector = context.inMemoryConnectors.find(
+ (connector) => connector.id === connectorId
+ );
+
+ const additionalPrivileges = inMemoryConnector?.isSystemAction
+ ? context.actionTypeRegistry.getSystemActionKibanaPrivileges(
+ inMemoryConnector.actionTypeId,
+ params
+ )
+ : [];
+
+ return additionalPrivileges;
+}
diff --git a/x-pack/plugins/actions/server/lib/is_preconfigured.ts b/x-pack/plugins/actions/server/lib/is_preconfigured.ts
new file mode 100644
index 0000000000000..9f42c496d7cb2
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/is_preconfigured.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 { ActionsClientContext } from '../actions_client';
+
+export function isPreconfigured(context: ActionsClientContext, connectorId: string): boolean {
+ return !!context.inMemoryConnectors.find(
+ (connector) => connector.isPreconfigured && connector.id === connectorId
+ );
+}
diff --git a/x-pack/plugins/actions/server/lib/is_system_action.ts b/x-pack/plugins/actions/server/lib/is_system_action.ts
new file mode 100644
index 0000000000000..e21e1ee480df8
--- /dev/null
+++ b/x-pack/plugins/actions/server/lib/is_system_action.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 { ActionsClientContext } from '../actions_client';
+
+export function isSystemAction(context: ActionsClientContext, connectorId: string): boolean {
+ return !!context.inMemoryConnectors.find(
+ (connector) => connector.isSystemAction && connector.id === connectorId
+ );
+}
diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/connector/execute/execute.test.ts
similarity index 85%
rename from x-pack/plugins/actions/server/routes/execute.test.ts
rename to x-pack/plugins/actions/server/routes/connector/execute/execute.test.ts
index 39319ff1dabf2..a9ae5e881f141 100644
--- a/x-pack/plugins/actions/server/routes/execute.test.ts
+++ b/x-pack/plugins/actions/server/routes/connector/execute/execute.test.ts
@@ -5,16 +5,16 @@
* 2.0.
*/
-import { executeActionRoute } from './execute';
+import { executeConnectorRoute } from './execute';
import { httpServiceMock } from '@kbn/core/server/mocks';
-import { licenseStateMock } from '../lib/license_state.mock';
-import { mockHandlerArguments } from './legacy/_mock_handler_arguments';
-import { asHttpRequestExecutionSource } from '../lib';
-import { actionsClientMock } from '../actions_client/actions_client.mock';
-import { ActionTypeExecutorResult } from '../types';
-import { verifyAccessAndContext } from './verify_access_and_context';
-
-jest.mock('./verify_access_and_context', () => ({
+import { licenseStateMock } from '../../../lib/license_state.mock';
+import { mockHandlerArguments } from '../../legacy/_mock_handler_arguments';
+import { asHttpRequestExecutionSource } from '../../../lib';
+import { actionsClientMock } from '../../../actions_client/actions_client.mock';
+import { ActionTypeExecutorResult } from '../../../types';
+import { verifyAccessAndContext } from '../../verify_access_and_context';
+
+jest.mock('../../verify_access_and_context', () => ({
verifyAccessAndContext: jest.fn(),
}));
@@ -23,7 +23,7 @@ beforeEach(() => {
(verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler);
});
-describe('executeActionRoute', () => {
+describe('executeConnectorRoute', () => {
beforeEach(() => {
jest.clearAllMocks();
});
@@ -55,7 +55,7 @@ describe('executeActionRoute', () => {
status: 'ok',
};
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
const [config, handler] = router.post.mock.calls[0];
@@ -95,7 +95,7 @@ describe('executeActionRoute', () => {
['noContent']
);
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@@ -131,7 +131,7 @@ describe('executeActionRoute', () => {
['ok']
);
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@@ -163,7 +163,7 @@ describe('executeActionRoute', () => {
['ok']
);
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@@ -192,7 +192,7 @@ describe('executeActionRoute', () => {
['ok']
);
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
const [_, handler] = router.post.mock.calls[0];
diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/connector/execute/execute.ts
similarity index 61%
rename from x-pack/plugins/actions/server/routes/execute.ts
rename to x-pack/plugins/actions/server/routes/connector/execute/execute.ts
index 74813a73474ac..ab5ed25ff5f78 100644
--- a/x-pack/plugins/actions/server/routes/execute.ts
+++ b/x-pack/plugins/actions/server/routes/connector/execute/execute.ts
@@ -5,37 +5,23 @@
* 2.0.
*/
-import { schema } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
-import { ILicenseState } from '../lib';
+import { ILicenseState } from '../../../lib';
-import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types';
-import { BASE_ACTION_API_PATH, RewriteResponseCase } from '../../common';
-import { asHttpRequestExecutionSource } from '../lib/action_execution_source';
-import { verifyAccessAndContext } from './verify_access_and_context';
-import { connectorResponseSchemaV1 } from '../../common/routes/connector/response';
+import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../../../types';
+import { BASE_ACTION_API_PATH } from '../../../../common';
+import { asHttpRequestExecutionSource } from '../../../lib/action_execution_source';
+import { verifyAccessAndContext } from '../../verify_access_and_context';
+import { connectorResponseSchemaV1 } from '../../../../common/routes/connector/response';
+import {
+ executeConnectorRequestBodySchemaV1,
+ ExecuteConnectorRequestBodyV1,
+ executeConnectorRequestParamsSchemaV1,
+ ExecuteConnectorRequestParamsV1,
+} from '../../../../common/routes/connector/apis/execute';
+import { transformExecuteConnectorResponseV1 } from './transforms';
-const paramSchema = schema.object({
- id: schema.string({
- meta: { description: 'An identifier for the connector.' },
- }),
-});
-
-const bodySchema = schema.object({
- params: schema.recordOf(schema.string(), schema.any()),
-});
-
-const rewriteBodyRes: RewriteResponseCase> = ({
- actionId,
- serviceMessage,
- ...res
-}) => ({
- ...res,
- connector_id: actionId,
- ...(serviceMessage ? { service_message: serviceMessage } : {}),
-});
-
-export const executeActionRoute = (
+export const executeConnectorRoute = (
router: IRouter,
licenseState: ILicenseState
) => {
@@ -51,8 +37,8 @@ export const executeActionRoute = (
},
validate: {
request: {
- body: bodySchema,
- params: paramSchema,
+ body: executeConnectorRequestBodySchemaV1,
+ params: executeConnectorRequestParamsSchemaV1,
},
response: {
200: {
@@ -65,8 +51,8 @@ export const executeActionRoute = (
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const actionsClient = (await context.actions).getActionsClient();
- const { params } = req.body;
- const { id } = req.params;
+ const { params }: ExecuteConnectorRequestBodyV1 = req.body;
+ const { id }: ExecuteConnectorRequestParamsV1 = req.params;
if (actionsClient.isSystemAction(id)) {
return res.badRequest({ body: 'Execution of system action is not allowed' });
@@ -81,7 +67,7 @@ export const executeActionRoute = (
return body
? res.ok({
- body: rewriteBodyRes(body),
+ body: transformExecuteConnectorResponseV1(body),
})
: res.noContent();
})
diff --git a/x-pack/plugins/actions/server/routes/connector/execute/index.ts b/x-pack/plugins/actions/server/routes/connector/execute/index.ts
new file mode 100644
index 0000000000000..6f5cb866722b7
--- /dev/null
+++ b/x-pack/plugins/actions/server/routes/connector/execute/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { executeConnectorRoute } from './execute';
diff --git a/x-pack/plugins/actions/server/routes/connector/execute/transforms/index.ts b/x-pack/plugins/actions/server/routes/connector/execute/transforms/index.ts
new file mode 100644
index 0000000000000..5c245970ec914
--- /dev/null
+++ b/x-pack/plugins/actions/server/routes/connector/execute/transforms/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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { transformExecuteConnectorResponse } from './transform_connector_response/latest';
+
+export { transformExecuteConnectorResponse as transformExecuteConnectorResponseV1 } from './transform_connector_response/v1';
diff --git a/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/latest.ts b/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/latest.ts
new file mode 100644
index 0000000000000..900d86f842fc6
--- /dev/null
+++ b/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/latest.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { transformExecuteConnectorResponse } from './v1';
diff --git a/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/v1.ts b/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/v1.ts
new file mode 100644
index 0000000000000..bc001cd9f9103
--- /dev/null
+++ b/x-pack/plugins/actions/server/routes/connector/execute/transforms/transform_connector_response/v1.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 { ConnectorExecuteResponseV1 } from '../../../../../../common/routes/connector/response';
+import { ActionTypeExecutorResult } from '../../../../../types';
+
+export const transformExecuteConnectorResponse = ({
+ actionId,
+ retry,
+ serviceMessage,
+ ...res
+}: ActionTypeExecutorResult): ConnectorExecuteResponseV1 => ({
+ ...res,
+ connector_id: actionId,
+ ...(retry && retry instanceof Date ? { retry: retry.toISOString() } : { retry }),
+ ...(serviceMessage ? { service_message: serviceMessage } : {}),
+});
diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts
index cccca87d849e2..5ea804d1ce47e 100644
--- a/x-pack/plugins/actions/server/routes/index.ts
+++ b/x-pack/plugins/actions/server/routes/index.ts
@@ -15,7 +15,7 @@ import { ILicenseState } from '../lib';
import { ActionsRequestHandlerContext } from '../types';
import { createActionRoute } from './create';
import { deleteConnectorRoute } from './connector/delete';
-import { executeActionRoute } from './execute';
+import { executeConnectorRoute } from './connector/execute';
import { getConnectorRoute } from './connector/get';
import { updateConnectorRoute } from './connector/update';
import { getOAuthAccessToken } from './get_oauth_access_token';
@@ -42,7 +42,7 @@ export function defineRoutes(opts: RouteOptions) {
getAllConnectorsRoute(router, licenseState);
updateConnectorRoute(router, licenseState);
listTypesRoute(router, licenseState);
- executeActionRoute(router, licenseState);
+ executeConnectorRoute(router, licenseState);
getGlobalExecutionLogRoute(router, licenseState);
getGlobalExecutionKPIRoute(router, licenseState);
diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx
index 222b64e4175e9..92ae3ebb6a00e 100644
--- a/x-pack/plugins/canvas/public/application.tsx
+++ b/x-pack/plugins/canvas/public/application.tsx
@@ -95,8 +95,8 @@ export const initializeCanvas = async (
// Some of these functions have deep dependencies into Canvas, which was bulking up the size
// of our bundle entry point. Moving them here pushes that load to when canvas is actually loaded.
const canvasFunctions = initFunctions({
+ http: coreSetup.http,
timefilter: setupPlugins.data.query.timefilter.timefilter,
- prependBasePath: coreStart.http.basePath.prepend,
types: setupPlugins.expressions.getTypes(),
paletteService: await setupPlugins.charts.palettes.getPalettes(),
});
diff --git a/x-pack/plugins/canvas/public/functions/index.ts b/x-pack/plugins/canvas/public/functions/index.ts
index ad91d6b98fa7f..15985b0fa7628 100644
--- a/x-pack/plugins/canvas/public/functions/index.ts
+++ b/x-pack/plugins/canvas/public/functions/index.ts
@@ -15,7 +15,7 @@ import { plotFunctionFactory } from './plot';
import { pieFunctionFactory } from './pie';
export interface InitializeArguments {
- prependBasePath: CoreSetup['http']['basePath']['prepend'];
+ http: CoreSetup['http'];
paletteService: PaletteRegistry;
types: ReturnType;
timefilter: CanvasSetupDeps['data']['query']['timefilter']['timefilter'];
diff --git a/x-pack/plugins/canvas/public/functions/timelion.ts b/x-pack/plugins/canvas/public/functions/timelion.ts
index 7ab272911389d..8f2e2c6562bc0 100644
--- a/x-pack/plugins/canvas/public/functions/timelion.ts
+++ b/x-pack/plugins/canvas/public/functions/timelion.ts
@@ -11,7 +11,6 @@ import { i18n } from '@kbn/i18n';
import type { TimeRange } from '@kbn/es-query';
import { ExpressionFunctionDefinition, DatatableRow } from '@kbn/expressions-plugin/public';
-import { fetch } from '../../common/lib/fetch';
// @ts-expect-error untyped local
import { buildBoolArray } from '../../common/lib/build_bool_array';
import { Datatable, ExpressionValueFilter } from '../../types';
@@ -132,16 +131,14 @@ export function timelionFunctionFactory(initialize: InitializeArguments): () =>
let result: any;
try {
- result = await fetch(initialize.prependBasePath(`/internal/timelion/run`), {
- method: 'POST',
- responseType: 'json',
- data: body,
+ result = await initialize.http.post(`/internal/timelion/run`, {
+ body: JSON.stringify(body),
});
} catch (e) {
throw errors.timelionError();
}
- const seriesList = result.data.sheet[0].list;
+ const seriesList = result.sheet[0].list;
const rows = flatten(
seriesList.map((series: { data: any[]; label: string }) =>
series.data.map((row) => ({
diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
index 4d453f64b6954..4f486d3337632 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
@@ -13,11 +13,11 @@ import { ConfigSchema } from '../config';
import { encryptionKeyRotationServiceMock } from '../crypto/index.mock';
export const routeDefinitionParamsMock = {
- create: (config: Record = {}) => ({
+ create: (config: Record = {}, buildFlavor: BuildFlavor = 'traditional') => ({
router: httpServiceMock.createRouter(),
logger: loggingSystemMock.create().get(),
config: ConfigSchema.validate(config) as ConfigType,
encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(),
- buildFlavor: 'traditional' as BuildFlavor,
+ buildFlavor,
}),
};
diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts
index 9b9dba6108ff3..f387e94e80990 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts
@@ -44,6 +44,7 @@ describe('Key rotation routes', () => {
it('correctly defines route.', () => {
expect(routeConfig.options).toEqual({
+ access: 'public',
tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
summary: `Rotate a key for encrypted saved objects`,
description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
@@ -83,6 +84,25 @@ describe('Key rotation routes', () => {
);
});
+ it('defines route as internal when build flavor is serverless', () => {
+ const routeParamsMock = routeDefinitionParamsMock.create(
+ { keyRotation: { decryptionOnlyKeys: ['b'.repeat(32)] } },
+ 'serverless'
+ );
+ defineKeyRotationRoutes(routeParamsMock);
+ const [config] = routeParamsMock.router.post.mock.calls.find(
+ ([{ path }]) => path === '/api/encrypted_saved_objects/_rotate_key'
+ )!;
+
+ expect(config.options).toEqual({
+ access: 'internal',
+ tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
+ summary: `Rotate a key for encrypted saved objects`,
+ description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
+ NOTE: Bulk key rotation can consume a considerable amount of resources and hence only user with a superuser role can trigger it.`,
+ });
+ });
+
it('returns 400 if decryption only keys are not specified.', async () => {
const routeParamsMock = routeDefinitionParamsMock.create();
defineKeyRotationRoutes(routeParamsMock);
diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts
index 80907497010da..272e74c3a69cb 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts
@@ -41,7 +41,7 @@ export function defineKeyRotationRoutes({
},
options: {
tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
- access: buildFlavor === 'serverless' ? 'internal' : undefined,
+ access: buildFlavor === 'serverless' ? 'internal' : 'public',
summary: `Rotate a key for encrypted saved objects`,
description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
NOTE: Bulk key rotation can consume a considerable amount of resources and hence only user with a superuser role can trigger it.`,
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts
index 433b6e392c27e..d40d9975a8820 100644
--- a/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts
+++ b/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts
@@ -7,7 +7,7 @@
import { ElasticsearchClient, Logger } from '@kbn/core/server';
import { EntityDefinition } from '@kbn/entities-schema';
-import { generateHistoryIndexName, generateLatestIndexName } from './helpers/generate_component_id';
+import { generateLatestIndexName } from './helpers/generate_component_id';
export async function deleteIndices(
esClient: ElasticsearchClient,
@@ -15,15 +15,8 @@ export async function deleteIndices(
logger: Logger
) {
try {
- const { indices: historyIndices } = await esClient.indices.resolveIndex({
- name: `${generateHistoryIndexName(definition)}.*`,
- expand_wildcards: 'all',
- });
- const indices = [
- ...historyIndices.map(({ name }) => name),
- generateLatestIndexName(definition),
- ];
- await esClient.indices.delete({ index: indices, ignore_unavailable: true });
+ const index = generateLatestIndexName(definition);
+ await esClient.indices.delete({ index, ignore_unavailable: true });
} catch (e) {
logger.error(`Unable to remove entity definition index [${definition.id}}]`);
throw e;
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts
index d0e0410b6e422..f8e27353082d0 100644
--- a/x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts
+++ b/x-pack/plugins/entity_manager/server/lib/entities/uninstall_entity_definition.ts
@@ -10,63 +10,48 @@ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { EntityDefinition } from '@kbn/entities-schema';
import { Logger } from '@kbn/logging';
import { deleteEntityDefinition } from './delete_entity_definition';
-import { deleteIndices } from './delete_index';
import { deleteIngestPipelines } from './delete_ingest_pipeline';
-import { findEntityDefinitions } from './find_entity_definition';
import { deleteTemplates } from '../manage_index_templates';
import { stopTransforms } from './stop_transforms';
import { deleteTransforms } from './delete_transforms';
+import { EntityClient } from '../entity_client';
export async function uninstallEntityDefinition({
definition,
esClient,
soClient,
logger,
- deleteData = false,
}: {
definition: EntityDefinition;
esClient: ElasticsearchClient;
soClient: SavedObjectsClientContract;
logger: Logger;
- deleteData?: boolean;
}) {
await stopTransforms(esClient, definition, logger);
await deleteTransforms(esClient, definition, logger);
await deleteIngestPipelines(esClient, definition, logger);
- if (deleteData) {
- await deleteIndices(esClient, definition, logger);
- }
-
await deleteTemplates(esClient, definition, logger);
await deleteEntityDefinition(soClient, definition);
}
export async function uninstallBuiltInEntityDefinitions({
- esClient,
- soClient,
- logger,
+ entityClient,
deleteData = false,
}: {
- esClient: ElasticsearchClient;
- soClient: SavedObjectsClientContract;
- logger: Logger;
+ entityClient: EntityClient;
deleteData?: boolean;
}): Promise {
- const definitions = await findEntityDefinitions({
- soClient,
- esClient,
- builtIn: true,
- });
+ const { definitions } = await entityClient.getEntityDefinitions({ builtIn: true });
await Promise.all(
- definitions.map(async (definition) => {
- await uninstallEntityDefinition({ definition, esClient, soClient, logger, deleteData });
+ definitions.map(async ({ id }) => {
+ await entityClient.deleteEntityDefinition({ id, deleteData });
})
);
diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts
index b3bed05ec0939..d3ab9f7a9df8e 100644
--- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts
+++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts
@@ -21,6 +21,7 @@ import { EntityDefinitionNotFound } from './entities/errors/entity_not_found';
import { stopTransforms } from './entities/stop_transforms';
import { findEntities } from './entities/find_entities';
+import { deleteIndices } from './entities/delete_index';
import { EntityDefinitionWithState } from './entities/types';
import { EntityDefinitionUpdateConflict } from './entities/errors/entity_definition_update_conflict';
@@ -40,19 +41,16 @@ export class EntityClient {
definition: EntityDefinition;
installOnly?: boolean;
}) {
+ const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser;
const installedDefinition = await installEntityDefinition({
definition,
+ esClient: secondaryAuthClient,
soClient: this.options.soClient,
- esClient: this.options.clusterClient.asSecondaryAuthUser,
logger: this.options.logger,
});
if (!installOnly) {
- await startTransforms(
- this.options.clusterClient.asSecondaryAuthUser,
- installedDefinition,
- this.options.logger
- );
+ await startTransforms(secondaryAuthClient, installedDefinition, this.options.logger);
}
return installedDefinition;
@@ -65,6 +63,7 @@ export class EntityClient {
id: string;
definitionUpdate: EntityDefinitionUpdate;
}) {
+ const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser;
const definition = await findEntityDefinitionById({
id,
soClient: this.options.soClient,
@@ -92,22 +91,21 @@ export class EntityClient {
definition,
definitionUpdate,
soClient: this.options.soClient,
- esClient: this.options.clusterClient.asSecondaryAuthUser,
+ esClient: secondaryAuthClient,
logger: this.options.logger,
});
if (shouldRestartTransforms) {
- await startTransforms(this.options.clusterClient.asSecondaryAuthUser, updatedDefinition, this.options.logger);
+ await startTransforms(secondaryAuthClient, updatedDefinition, this.options.logger);
}
return updatedDefinition;
}
async deleteEntityDefinition({ id, deleteData = false }: { id: string; deleteData?: boolean }) {
- const [definition] = await findEntityDefinitions({
+ const definition = await findEntityDefinitionById({
id,
- perPage: 1,
+ esClient: this.options.clusterClient.asCurrentUser,
soClient: this.options.soClient,
- esClient: this.options.clusterClient.asSecondaryAuthUser,
});
if (!definition) {
@@ -118,11 +116,20 @@ export class EntityClient {
await uninstallEntityDefinition({
definition,
- deleteData,
- soClient: this.options.soClient,
esClient: this.options.clusterClient.asSecondaryAuthUser,
+ soClient: this.options.soClient,
logger: this.options.logger,
});
+
+ if (deleteData) {
+ // delete data with current user as system user does not have
+ // .entities privileges
+ await deleteIndices(
+ this.options.clusterClient.asCurrentUser,
+ definition,
+ this.options.logger
+ );
+ }
}
async getEntityDefinitions({
diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts
index 9c1c4f403636b..f8629fe46497b 100644
--- a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts
+++ b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts
@@ -49,7 +49,7 @@ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({
deleteData: z.optional(BooleanFromString).default(false),
}),
}),
- handler: async ({ context, response, params, logger, server }) => {
+ handler: async ({ context, request, response, params, logger, server, getScopedClient }) => {
try {
const esClientAsCurrentUser = (await context.core).elasticsearch.client.asCurrentUser;
const canDisable = await canDisableEntityDiscovery(esClientAsCurrentUser);
@@ -62,15 +62,13 @@ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({
});
}
- const esClient = (await context.core).elasticsearch.client.asSecondaryAuthUser;
+ const entityClient = await getScopedClient({ request });
const soClient = (await context.core).savedObjects.getClient({
includedHiddenTypes: [EntityDiscoveryApiKeyType.name],
});
await uninstallBuiltInEntityDefinitions({
- soClient,
- esClient,
- logger,
+ entityClient,
deleteData: params.query.deleteData,
});
diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts
index 1797c30d15f4d..746498221de55 100644
--- a/x-pack/plugins/fleet/server/config.ts
+++ b/x-pack/plugins/fleet/server/config.ts
@@ -26,7 +26,7 @@ import { BULK_CREATE_MAX_ARTIFACTS_BYTES } from './services/artifacts/artifacts'
const DEFAULT_BUNDLED_PACKAGE_LOCATION = path.join(__dirname, '../target/bundled_packages');
const DEFAULT_GPG_KEY_PATH = path.join(__dirname, '../target/keys/GPG-KEY-elasticsearch');
-const REGISTRY_SPEC_MAX_VERSION = '3.2';
+const REGISTRY_SPEC_MAX_VERSION = '3.3';
export const config: PluginConfigDescriptor = {
exposeToBrowser: {
diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts
index 36b6d4fdbeb17..fb2153ff903f0 100644
--- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts
+++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts
@@ -902,116 +902,6 @@ describe('policy preconfiguration', () => {
);
});
- it('should return a non fatal error if support_agentless is defined in stateful', async () => {
- const soClient = getPutPreconfiguredPackagesMock();
- const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
- jest.mocked(appContextService).getInternalUserSOClientForSpaceId.mockReturnValue(soClient);
- jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({
- agentless: true,
- } as any);
-
- jest
- .spyOn(appContextService, 'getCloud')
- .mockReturnValue({ isServerlessEnabled: false } as any);
-
- const policies: PreconfiguredAgentPolicy[] = [
- {
- name: 'Test policy',
- namespace: 'default',
- id: 'test-id',
- supports_agentless: true,
- package_policies: [],
- },
- ];
-
- const { nonFatalErrors } = await ensurePreconfiguredPackagesAndPolicies(
- soClient,
- esClient,
- policies,
- [{ name: 'CANNOT_MATCH', version: 'x.y.z' }],
- mockDefaultOutput,
- mockDefaultDownloadService,
- DEFAULT_SPACE_ID
- );
- // @ts-ignore-next-line
- expect(nonFatalErrors[0].error.toString()).toEqual(
- 'FleetError: `supports_agentless` is only allowed in serverless environments that support the agentless feature'
- );
- });
-
- it('should not return an error if support_agentless is defined in serverless and agentless is enabled', async () => {
- const soClient = getPutPreconfiguredPackagesMock();
- const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
- jest.mocked(appContextService).getInternalUserSOClientForSpaceId.mockReturnValue(soClient);
- jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({
- agentless: true,
- } as any);
-
- jest
- .spyOn(appContextService, 'getCloud')
- .mockReturnValue({ isServerlessEnabled: true } as any);
-
- const policies: PreconfiguredAgentPolicy[] = [
- {
- name: 'Test policy',
- namespace: 'default',
- id: 'test-id',
- supports_agentless: true,
- package_policies: [],
- },
- ];
-
- const { policies: resPolicies, nonFatalErrors } =
- await ensurePreconfiguredPackagesAndPolicies(
- soClient,
- esClient,
- policies,
- [{ name: 'CANNOT_MATCH', version: 'x.y.z' }],
- mockDefaultOutput,
- mockDefaultDownloadService,
- DEFAULT_SPACE_ID
- );
- expect(nonFatalErrors.length).toBe(0);
- expect(resPolicies[0].id).toEqual('test-id');
- });
-
- it('should return an error if agentless feature flag is disabled on serverless', async () => {
- const soClient = getPutPreconfiguredPackagesMock();
- const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
- jest.mocked(appContextService).getInternalUserSOClientForSpaceId.mockReturnValue(soClient);
- jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({
- agentless: false,
- } as any);
-
- jest
- .spyOn(appContextService, 'getCloud')
- .mockReturnValue({ isServerlessEnabled: true } as any);
-
- const policies: PreconfiguredAgentPolicy[] = [
- {
- name: 'Test policy',
- namespace: 'default',
- id: 'test-id',
- supports_agentless: true,
- package_policies: [],
- },
- ];
-
- const { nonFatalErrors } = await ensurePreconfiguredPackagesAndPolicies(
- soClient,
- esClient,
- policies,
- [{ name: 'CANNOT_MATCH', version: 'x.y.z' }],
- mockDefaultOutput,
- mockDefaultDownloadService,
- DEFAULT_SPACE_ID
- );
- // @ts-ignore-next-line
- expect(nonFatalErrors[0].error.toString()).toEqual(
- 'FleetError: `supports_agentless` is only allowed in serverless environments that support the agentless feature'
- );
- });
-
it('should not attempt to recreate or modify an agent policy if its ID is unchanged', async () => {
const soClient = getPutPreconfiguredPackagesMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts
index 18726cdab4452..cf0ee7e70ca15 100644
--- a/x-pack/plugins/fleet/server/services/preconfiguration.ts
+++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts
@@ -43,7 +43,6 @@ import { type InputsOverride, packagePolicyService } from './package_policy';
import { preconfigurePackageInputs } from './package_policy';
import { appContextService } from './app_context';
import type { UpgradeManagedPackagePoliciesResult } from './setup/managed_package_policies';
-import { isDefaultAgentlessPolicyEnabled } from './utils/agentless';
interface PreconfigurationResult {
policies: Array<{ id: string; updated_at: string }>;
@@ -163,18 +162,6 @@ export async function ensurePreconfiguredPackagesAndPolicies(
);
}
- if (
- !isDefaultAgentlessPolicyEnabled() &&
- preconfiguredAgentPolicy?.supports_agentless !== undefined
- ) {
- throw new FleetError(
- i18n.translate('xpack.fleet.preconfiguration.support_agentless', {
- defaultMessage:
- '`supports_agentless` is only allowed in serverless environments that support the agentless feature',
- })
- );
- }
-
const namespacedSoClient = preconfiguredAgentPolicy.space_id
? appContextService.getInternalUserSOClientForSpaceId(preconfiguredAgentPolicy.space_id)
: appContextService.getInternalUserSOClientForSpaceId(DEFAULT_NAMESPACE_STRING);
diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
index b1473c56b37ed..efc564089fb43 100644
--- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
+++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
@@ -173,7 +173,7 @@ export const SearchBar: FC = (opts) => {
reportEvent.searchRequest();
}
- const rawParams = parseSearchParams(searchValue.toLowerCase());
+ const rawParams = parseSearchParams(searchValue.toLowerCase(), searchableTypes);
let tagIds: string[] | undefined;
if (taggingApi && rawParams.filters.tags) {
tagIds = rawParams.filters.tags.map(
diff --git a/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.test.ts b/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.test.ts
index c6df745be847f..8e24f599ce1d2 100644
--- a/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.test.ts
+++ b/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.test.ts
@@ -9,12 +9,12 @@ import { parseSearchParams } from './parse_search_params';
describe('parseSearchParams', () => {
it('returns the correct term', () => {
- const searchParams = parseSearchParams('tag:(my-tag OR other-tag) hello');
+ const searchParams = parseSearchParams('tag:(my-tag OR other-tag) hello', []);
expect(searchParams.term).toEqual('hello');
});
it('returns the raw query as `term` in case of parsing error', () => {
- const searchParams = parseSearchParams('tag:((()^invalid');
+ const searchParams = parseSearchParams('tag:((()^invalid', []);
expect(searchParams).toEqual({
term: 'tag:((()^invalid',
filters: {},
@@ -22,12 +22,12 @@ describe('parseSearchParams', () => {
});
it('returns `undefined` term if query only contains field clauses', () => {
- const searchParams = parseSearchParams('tag:(my-tag OR other-tag)');
+ const searchParams = parseSearchParams('tag:(my-tag OR other-tag)', []);
expect(searchParams.term).toBeUndefined();
});
it('returns correct filters when no field clause is defined', () => {
- const searchParams = parseSearchParams('hello');
+ const searchParams = parseSearchParams('hello', []);
expect(searchParams.filters).toEqual({
tags: undefined,
types: undefined,
@@ -35,7 +35,7 @@ describe('parseSearchParams', () => {
});
it('returns correct filters when field clauses are present', () => {
- const searchParams = parseSearchParams('tag:foo type:bar hello tag:dolly');
+ const searchParams = parseSearchParams('tag:foo type:bar hello tag:dolly', []);
expect(searchParams).toEqual({
term: 'hello',
filters: {
@@ -46,7 +46,7 @@ describe('parseSearchParams', () => {
});
it('considers unknown field clauses to be part of the raw search term', () => {
- const searchParams = parseSearchParams('tag:foo unknown:bar hello');
+ const searchParams = parseSearchParams('tag:foo unknown:bar hello', []);
expect(searchParams).toEqual({
term: 'unknown:bar hello',
filters: {
@@ -56,7 +56,7 @@ describe('parseSearchParams', () => {
});
it('handles aliases field clauses', () => {
- const searchParams = parseSearchParams('tag:foo tags:bar type:dash types:board hello');
+ const searchParams = parseSearchParams('tag:foo tags:bar type:dash types:board hello', []);
expect(searchParams).toEqual({
term: 'hello',
filters: {
@@ -67,7 +67,7 @@ describe('parseSearchParams', () => {
});
it('converts boolean and number values to string for known filters', () => {
- const searchParams = parseSearchParams('tag:42 tags:true type:69 types:false hello');
+ const searchParams = parseSearchParams('tag:42 tags:true type:69 types:false hello', []);
expect(searchParams).toEqual({
term: 'hello',
filters: {
@@ -76,4 +76,74 @@ describe('parseSearchParams', () => {
},
});
});
+
+ it('converts multiword searchable types to phrases so they get picked up as types', () => {
+ const mockSearchableMultiwordTypes = ['canvas-workpad', 'enterprise search'];
+ const searchParams = parseSearchParams(
+ 'type:canvas workpad types:canvas-workpad hello type:enterprise search type:not multiword',
+ mockSearchableMultiwordTypes
+ );
+ expect(searchParams).toEqual({
+ term: 'hello multiword',
+ filters: {
+ types: ['canvas workpad', 'enterprise search', 'not'],
+ },
+ });
+ });
+
+ it('parses correctly when multiword types are already quoted', () => {
+ const mockSearchableMultiwordTypes = ['canvas-workpad'];
+ const searchParams = parseSearchParams(
+ `type:"canvas workpad" hello type:"dashboard"`,
+ mockSearchableMultiwordTypes
+ );
+ expect(searchParams).toEqual({
+ term: 'hello',
+ filters: {
+ types: ['canvas workpad', 'dashboard'],
+ },
+ });
+ });
+
+ it('parses correctly when there is whitespace between type keyword and value', () => {
+ const mockSearchableMultiwordTypes = ['canvas-workpad'];
+ const searchParams = parseSearchParams(
+ 'type: canvas workpad hello type: dashboard',
+ mockSearchableMultiwordTypes
+ );
+ expect(searchParams).toEqual({
+ term: 'hello',
+ filters: {
+ types: ['canvas workpad', 'dashboard'],
+ },
+ });
+ });
+
+ it('dedupes duplicate types', () => {
+ const mockSearchableMultiwordTypes = ['canvas-workpad'];
+ const searchParams = parseSearchParams(
+ 'type:canvas workpad hello type:dashboard type:canvas-workpad type:canvas workpad type:dashboard',
+ mockSearchableMultiwordTypes
+ );
+ expect(searchParams).toEqual({
+ term: 'hello',
+ filters: {
+ types: ['canvas workpad', 'dashboard'],
+ },
+ });
+ });
+
+ it('handles whitespace removal even if there are no multiword types', () => {
+ const mockSearchableMultiwordTypes: string[] = [];
+ const searchParams = parseSearchParams(
+ 'hello type: dashboard',
+ mockSearchableMultiwordTypes
+ );
+ expect(searchParams).toEqual({
+ term: 'hello',
+ filters: {
+ types: ['dashboard'],
+ },
+ });
+ });
});
diff --git a/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.ts b/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.ts
index 1df6c1123a328..90ba36cce5fcb 100644
--- a/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.ts
+++ b/x-pack/plugins/global_search_bar/public/search_syntax/parse_search_params.ts
@@ -16,12 +16,54 @@ const aliasMap = {
type: ['types'],
};
-export const parseSearchParams = (term: string): ParsedSearchParams => {
+// Converts multiword types to phrases by wrapping them in quotes and trimming whitespace after type keyword. Example: type: canvas workpad -> type:"canvas workpad". If the type is already wrapped in quotes or is a single word, it will only trim whitespace after type keyword.
+const convertMultiwordTypesToPhrasesAndTrimWhitespace = (
+ term: string,
+ multiWordTypes: string[]
+): string => {
+ if (!multiWordTypes.length) {
+ return term.replace(
+ /(type:|types:)\s*([^"']*?)\b([^"'\s]+)/gi,
+ (_, typeKeyword, whitespace, typeValue) => `${typeKeyword}${whitespace.trim()}${typeValue}`
+ );
+ }
+
+ const typesPattern = multiWordTypes.join('|');
+ const termReplaceRegex = new RegExp(
+ `(type:|types:)\\s*([^"']*?)\\b((${typesPattern})\\b|[^\\s"']+)`,
+ 'gi'
+ );
+
+ return term.replace(termReplaceRegex, (_, typeKeyword, whitespace, typeValue) => {
+ const trimmedTypeKeyword = `${typeKeyword}${whitespace.trim()}`;
+
+ // If the type value is already wrapped in quotes, leave it as is
+ return /['"]/.test(typeValue)
+ ? `${trimmedTypeKeyword}${typeValue}`
+ : `${trimmedTypeKeyword}"${typeValue}"`;
+ });
+};
+
+const dedupeTypes = (types: FilterValues): FilterValues => [
+ ...new Set(types.map((item) => item.replace(/[-\s]+/g, ' ').trim())),
+];
+
+export const parseSearchParams = (term: string, searchableTypes: string[]): ParsedSearchParams => {
const recognizedFields = knownFilters.concat(...Object.values(aliasMap));
let query: Query;
+ // Finds all multiword types that are separated by whitespace or hyphens
+ const multiWordSearchableTypesWhitespaceSeperated = searchableTypes
+ .filter((item) => /[ -]/.test(item))
+ .map((item) => item.replace(/-/g, ' '));
+
+ const modifiedTerm = convertMultiwordTypesToPhrasesAndTrimWhitespace(
+ term,
+ multiWordSearchableTypesWhitespaceSeperated
+ );
+
try {
- query = Query.parse(term, {
+ query = Query.parse(modifiedTerm, {
schema: { recognizedFields },
});
} catch (e) {
@@ -42,7 +84,7 @@ export const parseSearchParams = (term: string): ParsedSearchParams => {
term: searchTerm,
filters: {
tags: tags ? valuesToString(tags) : undefined,
- types: types ? valuesToString(types) : undefined,
+ types: types ? dedupeTypes(valuesToString(types)) : undefined,
},
};
};
diff --git a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx
index 563797b2ae932..5a92a67962579 100644
--- a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx
+++ b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx
@@ -222,6 +222,7 @@ const ClickToDownloadTabContent: FC = ({
id="xpack.ml.trainedModels.addModelFlyout.e5Description"
defaultMessage="E5 is a third party NLP model that enables you to perform multi-lingual semantic search by using dense vector representations. This model performs best for non-English language documents and queries."
/>
+ {models[0].disclaimer}
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx
index 627c314bf72e6..a92d781e3dd7c 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_dashboards/index.tsx
@@ -40,12 +40,14 @@ import { useDashboardFetcher } from '../../../hooks/use_dashboards_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { APM_APP_LOCATOR_ID } from '../../../locator/service_detail_locator';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
+import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
+import { isLogsOnlySignal } from '../../../utils/get_signal_type';
export interface MergedServiceDashboard extends SavedApmCustomDashboard {
title: string;
}
-export function ServiceDashboards({ checkForEntities = false }: { checkForEntities?: boolean }) {
+export function ServiceDashboards() {
const {
path: { serviceName },
query: { environment, kuery, rangeFrom, rangeTo, dashboardId },
@@ -53,6 +55,10 @@ export function ServiceDashboards({ checkForEntities = false }: { checkForEntiti
'/services/{serviceName}/dashboards',
'/mobile-services/{serviceName}/dashboards'
);
+ const { serviceEntitySummary, serviceEntitySummaryStatus } = useApmServiceContext();
+ const checkForEntities = serviceEntitySummary?.dataStreamTypes
+ ? isLogsOnlySignal(serviceEntitySummary.dataStreamTypes)
+ : false;
const [dashboard, setDashboard] = useState();
const [serviceDashboards, setServiceDashboards] = useState([]);
const [currentDashboard, setCurrentDashboard] = useState();
@@ -150,7 +156,7 @@ export function ServiceDashboards({ checkForEntities = false }: { checkForEntiti
return (
- {status === FETCH_STATUS.LOADING ? (
+ {status === FETCH_STATUS.LOADING || serviceEntitySummaryStatus === FETCH_STATUS.LOADING ? (
}
title={
diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx
index 6f13c33585bca..7941881ff2c51 100644
--- a/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx
+++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_enablement/enable_entity_model_button.tsx
@@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { EntityManagerUnauthorizedError } from '@kbn/entityManager-plugin/public';
import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
+import { TechnicalPreviewBadge } from '@kbn/observability-shared-plugin/public';
import { useKibana } from '../../hooks/use_kibana';
import { Unauthorized } from './unauthorized_modal';
@@ -57,6 +58,8 @@ export function EnableEntityModelButton({ onSuccess }: { onSuccess: () => void }
data-test-subj="inventoryInventoryPageTemplateFilledButton"
fill
onClick={handleEnablement}
+ iconType={() => }
+ iconSide="right"
>
{i18n.translate('xpack.inventory.noData.card.button', {
defaultMessage: 'Enable',
diff --git a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx
index 3b12e11d2ba7c..79db53f39c346 100644
--- a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx
+++ b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/no_data_config.tsx
@@ -33,8 +33,20 @@ export function getEntityManagerEnablement({
description: (
+ {i18n.translate('xpack.inventory.noData.card.description.inventory', {
+ defaultMessage: 'Inventory',
+ })}
+
+ ),
link: (
{
+ let dispatchMock: jest.Mock;
+ beforeEach(() => {
+ dispatchMock = jest.fn();
+ jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(dispatchMock);
+ jest.spyOn(selectedLocationHook, 'useSelectedLocation').mockReturnValue({
+ id: 'us-east-1',
+ label: 'us-east-1',
+ isServiceManaged: true,
+ });
+ jest.spyOn(selectedMonitorHook, 'useSelectedMonitor').mockReturnValue({
+ monitor: {
+ id: 'testMonitorId',
+ type: 'browser',
+ name: 'testMonitor',
+ enabled: true,
+ schedule: {
+ number: 5,
+ unit: 'm',
+ },
+ locations: ['us-east-1'],
+ tags: [],
+ apiKey: '1234',
+ config: {
+ synthetics: {
+ type: 'simple',
+ timeout: 10,
+ frequency: 5,
+ url: 'http://elastic.co',
+ method: 'GET',
+ request: {
+ headers: {},
+ },
+ response: {
+ status: 200,
+ },
+ },
+ },
+ },
+ } as any);
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('does not request status data when interval is invalid', async () => {
+ const props = {
+ from: 1728310613654,
+ to: 1728311513654,
+ initialSizeRef: { current: { clientWidth: 0 } as any },
+ };
+ const { result } = renderHook(() => useMonitorStatusData(props), {
+ wrapper: WrappedHelper,
+ });
+ expect(result.current).toBeDefined();
+ expect(result.current.minsPerBin).toBeNull();
+ expect(
+ dispatchMock.mock.calls.some((args) => args[0].type === 'QUIET GET MONITOR STATUS HEATMAP')
+ ).not.toBe(true);
+ });
+
+ it('handles resize events and requests based on new data', async () => {
+ const props = {
+ from: 1728310613654,
+ to: 1728317313654,
+ initialSizeRef: { current: { clientWidth: 0 } as any },
+ };
+ const { result } = renderHook(() => useMonitorStatusData(props), {
+ wrapper: WrappedHelper,
+ });
+ await act(async () => {
+ result.current.handleResize({ width: 250, height: 800 });
+ // this is necessary for debounce to complete
+ await new Promise((r) => setTimeout(r, 510));
+ });
+ const fetchActions = dispatchMock.mock.calls.filter(
+ (args) => args[0].type === 'QUIET GET MONITOR STATUS HEATMAP'
+ );
+ expect(fetchActions).toHaveLength(1);
+ expect(omit(fetchActions[0][0], 'meta')).toMatchInlineSnapshot(`
+ Object {
+ "payload": Object {
+ "from": 1728310613654,
+ "interval": 7,
+ "location": "us-east-1",
+ "monitorId": "testMonitorId",
+ "to": 1728317313654,
+ },
+ "type": "QUIET GET MONITOR STATUS HEATMAP",
+ }
+ `);
+ });
+});
+
+describe('useBins', () => {
+ it('generates bins and overlays histogram data', () => {
+ const { result } = renderHook(
+ () =>
+ useBins({
+ minsPerBin: 5,
+ fromMillis: 1728310613654,
+ toMillis: 1728313563654,
+ dateHistogram: [
+ {
+ key: 1728310613654,
+ key_as_string: '2023-06-06T00:56:53.654Z',
+ doc_count: 1,
+ down: {
+ value: 0,
+ },
+ up: {
+ value: 1,
+ },
+ },
+ {
+ key: 1728310613654 + 300000,
+ key_as_string: '2023-06-06T00:56:53.654Z',
+ doc_count: 1,
+ down: {
+ value: 0,
+ },
+ up: {
+ value: 1,
+ },
+ },
+ {
+ key: 1728310613654 + 600000,
+ key_as_string: '2023-06-06T00:56:53.654Z',
+ doc_count: 1,
+ down: {
+ value: 1,
+ },
+ up: {
+ value: 0,
+ },
+ },
+ {
+ key: 1728310613654 + 900000,
+ key_as_string: '2023-06-06T00:56:53.654Z',
+ doc_count: 1,
+ down: {
+ value: 2,
+ },
+ up: {
+ value: 1,
+ },
+ },
+ ],
+ }),
+ { wrapper: WrappedHelper }
+ );
+ expect(result.current).toMatchInlineSnapshot(`
+ Object {
+ "timeBinMap": Map {
+ 1728310800000 => Object {
+ "downs": 0,
+ "end": 1728310800000,
+ "start": 1728310500000,
+ "ups": 1,
+ "value": 1,
+ },
+ 1728311100000 => Object {
+ "downs": 0,
+ "end": 1728311100000,
+ "start": 1728310800000,
+ "ups": 1,
+ "value": 1,
+ },
+ 1728311400000 => Object {
+ "downs": 1,
+ "end": 1728311400000,
+ "start": 1728311100000,
+ "ups": 0,
+ "value": -1,
+ },
+ 1728311700000 => Object {
+ "downs": 2,
+ "end": 1728311700000,
+ "start": 1728311400000,
+ "ups": 1,
+ "value": -0.3333333333333333,
+ },
+ 1728312000000 => Object {
+ "downs": 0,
+ "end": 1728312000000,
+ "start": 1728311700000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728312300000 => Object {
+ "downs": 0,
+ "end": 1728312300000,
+ "start": 1728312000000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728312600000 => Object {
+ "downs": 0,
+ "end": 1728312600000,
+ "start": 1728312300000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728312900000 => Object {
+ "downs": 0,
+ "end": 1728312900000,
+ "start": 1728312600000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728313200000 => Object {
+ "downs": 0,
+ "end": 1728313200000,
+ "start": 1728312900000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728313500000 => Object {
+ "downs": 0,
+ "end": 1728313500000,
+ "start": 1728313200000,
+ "ups": 0,
+ "value": 0,
+ },
+ 1728313800000 => Object {
+ "downs": 0,
+ "end": 1728313800000,
+ "start": 1728313500000,
+ "ups": 0,
+ "value": 0,
+ },
+ },
+ "timeBins": Array [
+ Object {
+ "downs": 0,
+ "end": 1728310800000,
+ "start": 1728310500000,
+ "ups": 1,
+ "value": 1,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728311100000,
+ "start": 1728310800000,
+ "ups": 1,
+ "value": 1,
+ },
+ Object {
+ "downs": 1,
+ "end": 1728311400000,
+ "start": 1728311100000,
+ "ups": 0,
+ "value": -1,
+ },
+ Object {
+ "downs": 2,
+ "end": 1728311700000,
+ "start": 1728311400000,
+ "ups": 1,
+ "value": -0.3333333333333333,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728312000000,
+ "start": 1728311700000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728312300000,
+ "start": 1728312000000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728312600000,
+ "start": 1728312300000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728312900000,
+ "start": 1728312600000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728313200000,
+ "start": 1728312900000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728313500000,
+ "start": 1728313200000,
+ "ups": 0,
+ "value": 0,
+ },
+ Object {
+ "downs": 0,
+ "end": 1728313800000,
+ "start": 1728313500000,
+ "ups": 0,
+ "value": 0,
+ },
+ ],
+ "xDomain": Object {
+ "max": 1728313800000,
+ "min": 1728310800000,
+ },
+ }
+ `);
+ });
+
+ it('returns a default value if interval is not valid', () => {
+ const { result } = renderHook(
+ () =>
+ useBins({
+ minsPerBin: null,
+ fromMillis: 1728310613654,
+ toMillis: 1728313563654,
+ }),
+ { wrapper: WrappedHelper }
+ );
+ expect(result.current).toMatchInlineSnapshot(`
+ Object {
+ "timeBinMap": Map {},
+ "timeBins": Array [],
+ "xDomain": Object {
+ "max": 1728313563654,
+ "min": 1728310613654,
+ },
+ }
+ `);
+ });
+});
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
index 710ff65de7c66..160287f9a3683 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
@@ -28,6 +28,7 @@ import {
quietGetMonitorStatusHeatmapAction,
selectHeatmap,
} from '../../../state/status_heatmap';
+import type { MonitorStatusHeatmapBucket } from '../../../../../../common/runtime_types';
type Props = Pick & {
initialSizeRef?: React.MutableRefObject;
@@ -99,7 +100,36 @@ export const useMonitorStatusData = ({ from, to, initialSizeRef }: Props) => {
[binsAvailableByWidth]
);
- const { timeBins, timeBinMap, xDomain } = useMemo((): {
+ const { timeBins, timeBinMap, xDomain } = useBins({
+ fromMillis,
+ toMillis,
+ dateHistogram,
+ minsPerBin,
+ });
+
+ return {
+ loading,
+ minsPerBin,
+ timeBins,
+ getTimeBinByXValue: (xValue: number | undefined) =>
+ xValue === undefined ? undefined : timeBinMap.get(xValue),
+ xDomain,
+ handleResize,
+ };
+};
+
+export const useBins = ({
+ minsPerBin,
+ fromMillis,
+ toMillis,
+ dateHistogram,
+}: {
+ minsPerBin: number | null;
+ fromMillis: number;
+ toMillis: number;
+ dateHistogram?: MonitorStatusHeatmapBucket[];
+}) =>
+ useMemo((): {
timeBins: MonitorStatusTimeBin[];
timeBinMap: Map;
xDomain: { min: number; max: number };
@@ -125,14 +155,3 @@ export const useMonitorStatusData = ({ from, to, initialSizeRef }: Props) => {
},
};
}, [minsPerBin, fromMillis, toMillis, dateHistogram]);
-
- return {
- loading,
- minsPerBin,
- timeBins,
- getTimeBinByXValue: (xValue: number | undefined) =>
- xValue === undefined ? undefined : timeBinMap.get(xValue),
- xDomain,
- handleResize,
- };
-};
diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts
index c494389eb7c13..0fd2c54a1e6ca 100644
--- a/x-pack/plugins/security/server/routes/authentication/common.test.ts
+++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts
@@ -69,6 +69,7 @@ describe('Common authentication routes', () => {
access: 'public',
authRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
+ excludeFromOAS: true,
});
expect(routeConfig.validate).toEqual({
body: undefined,
@@ -170,7 +171,7 @@ describe('Common authentication routes', () => {
});
it('correctly defines route.', async () => {
- expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.options).toEqual({ access: 'internal' });
expect(routeConfig.validate).toBe(false);
});
diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts
index e54d6e35f1669..b519171fd4fe6 100644
--- a/x-pack/plugins/security/server/routes/authentication/common.ts
+++ b/x-pack/plugins/security/server/routes/authentication/common.ts
@@ -48,6 +48,7 @@ export function defineCommonRoutes({
validate: { query: schema.object({}, { unknowns: 'allow' }) },
options: {
access: 'public',
+ excludeFromOAS: true,
authRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
},
@@ -89,10 +90,11 @@ export function defineCommonRoutes({
'/internal/security/me',
...(buildFlavor !== 'serverless' ? ['/api/security/v1/me'] : []),
]) {
+ const deprecated = path === '/api/security/v1/me';
router.get(
- { path, validate: false },
+ { path, validate: false, options: { access: deprecated ? 'public' : 'internal' } },
createLicensedRouteHandler(async (context, request, response) => {
- if (path === '/api/security/v1/me') {
+ if (deprecated) {
logger.warn(
`The "${basePath.serverBasePath}${path}" endpoint is deprecated and will be removed in the next major version.`,
{ tags: ['deprecation'] }
diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts
index 2c4ab9de1491b..69c3ce1700671 100644
--- a/x-pack/plugins/security/server/routes/authentication/oidc.ts
+++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts
@@ -37,7 +37,7 @@ export function defineOIDCRoutes({
{
path,
validate: false,
- options: { authRequired: false },
+ options: { authRequired: false, excludeFromOAS: true },
},
(context, request, response) => {
const serverBasePath = basePath.serverBasePath;
@@ -68,7 +68,7 @@ export function defineOIDCRoutes({
{
path: '/internal/security/oidc/implicit.js',
validate: false,
- options: { authRequired: false },
+ options: { authRequired: false, excludeFromOAS: true },
},
(context, request, response) => {
const serverBasePath = basePath.serverBasePath;
@@ -106,7 +106,12 @@ export function defineOIDCRoutes({
{ unknowns: 'allow' }
),
},
- options: { authRequired: false, tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW] },
+ options: {
+ access: 'public',
+ excludeFromOAS: true,
+ authRequired: false,
+ tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
+ },
},
createLicensedRouteHandler(async (context, request, response) => {
const serverBasePath = basePath.serverBasePath;
@@ -184,6 +189,8 @@ export function defineOIDCRoutes({
),
},
options: {
+ access: 'public',
+ excludeFromOAS: true,
authRequired: false,
xsrfRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
@@ -227,6 +234,8 @@ export function defineOIDCRoutes({
),
},
options: {
+ access: 'public',
+ excludeFromOAS: true,
authRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
},
diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts
index e952d98a38649..f693d20354e89 100644
--- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts
+++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts
@@ -56,6 +56,7 @@ describe('SAML authentication routes', () => {
expect(routeConfig.options).toEqual({
access: 'public',
authRequired: false,
+ excludeFromOAS: true,
xsrfRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
});
diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts
index ddc31fbc88b89..3c72fd908e6c4 100644
--- a/x-pack/plugins/security/server/routes/authentication/saml.ts
+++ b/x-pack/plugins/security/server/routes/authentication/saml.ts
@@ -38,6 +38,7 @@ export function defineSAMLRoutes({
},
options: {
access: 'public',
+ excludeFromOAS: true,
authRequired: false,
xsrfRequired: false,
tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW],
diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.ts
index 1d278aa676ac3..b7204faaa7ca4 100644
--- a/x-pack/plugins/security/server/routes/authorization/privileges/get.ts
+++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.ts
@@ -26,6 +26,7 @@ export function defineGetPrivilegesRoutes({ router, authz }: RouteDefinitionPara
),
}),
},
+ options: { access: 'public' },
},
createLicensedRouteHandler((context, request, response) => {
const respectLicenseLevel = request.query.respectLicenseLevel !== 'false'; // if undefined resolve to true by default
diff --git a/x-pack/plugins/security/server/routes/authorization/reset_session_page.ts b/x-pack/plugins/security/server/routes/authorization/reset_session_page.ts
index 67254735b9a16..0af24ad8d8397 100644
--- a/x-pack/plugins/security/server/routes/authorization/reset_session_page.ts
+++ b/x-pack/plugins/security/server/routes/authorization/reset_session_page.ts
@@ -12,7 +12,7 @@ export function resetSessionPageRoutes({ httpResources }: RouteDefinitionParams)
{
path: '/internal/security/reset_session_page.js',
validate: false,
- options: { authRequired: false },
+ options: { authRequired: false, excludeFromOAS: true },
},
(context, request, response) => {
return response.renderJs({
diff --git a/x-pack/plugins/security/server/routes/session_management/index.ts b/x-pack/plugins/security/server/routes/session_management/index.ts
index c095a77409975..041feea8a62fd 100644
--- a/x-pack/plugins/security/server/routes/session_management/index.ts
+++ b/x-pack/plugins/security/server/routes/session_management/index.ts
@@ -13,12 +13,5 @@ import type { RouteDefinitionParams } from '..';
export function defineSessionManagementRoutes(params: RouteDefinitionParams) {
defineSessionInfoRoutes(params);
defineSessionExtendRoutes(params);
-
- // The invalidate session API was introduced to address situations where the session index
- // could grow rapidly - when session timeouts are disabled, or with anonymous access.
- // In the serverless environment, sessions timeouts are always be enabled, and there is no
- // anonymous access. This eliminates the need for an invalidate session HTTP API.
- if (params.buildFlavor !== 'serverless') {
- defineInvalidateSessionsRoutes(params);
- }
+ defineInvalidateSessionsRoutes(params);
}
diff --git a/x-pack/plugins/security/server/routes/session_management/invalidate.ts b/x-pack/plugins/security/server/routes/session_management/invalidate.ts
index c7d27b835edf2..a45d8f00c1ca4 100644
--- a/x-pack/plugins/security/server/routes/session_management/invalidate.ts
+++ b/x-pack/plugins/security/server/routes/session_management/invalidate.ts
@@ -12,7 +12,11 @@ import type { RouteDefinitionParams } from '..';
/**
* Defines routes required for session invalidation.
*/
-export function defineInvalidateSessionsRoutes({ router, getSession }: RouteDefinitionParams) {
+export function defineInvalidateSessionsRoutes({
+ router,
+ getSession,
+ buildFlavor,
+}: RouteDefinitionParams) {
router.post(
{
path: '/api/security/session/_invalidate',
@@ -34,7 +38,12 @@ export function defineInvalidateSessionsRoutes({ router, getSession }: RouteDefi
}),
},
options: {
- access: 'public',
+ // The invalidate session API was introduced to address situations where the session index
+ // could grow rapidly - when session timeouts are disabled, or with anonymous access.
+ // In the serverless environment, sessions timeouts are always be enabled, and there is no
+ // anonymous access. However, keeping this endpoint available internally in serverless would
+ // be useful in situations where we need to batch-invalidate user sessions.
+ access: buildFlavor === 'serverless' ? 'internal' : 'public',
tags: ['access:sessionManagement'],
summary: `Invalidate user sessions`,
},
diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts
index ef588ae1cfcfc..74eee1e129e2c 100644
--- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts
+++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts
@@ -71,7 +71,7 @@ describe('Access agreement view routes', () => {
});
it('correctly defines route.', () => {
- expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.options).toEqual({ excludeFromOAS: true });
expect(routeConfig.validate).toBe(false);
});
diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts
index 3724892edd6df..823fbb0286f33 100644
--- a/x-pack/plugins/security/server/routes/views/access_agreement.ts
+++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts
@@ -24,7 +24,7 @@ export function defineAccessAgreementRoutes({
const canHandleRequest = () => license.getFeatures().allowAccessAgreement;
httpResources.register(
- { path: '/security/access_agreement', validate: false },
+ { path: '/security/access_agreement', validate: false, options: { excludeFromOAS: true } },
createLicensedRouteHandler(async (context, request, response) =>
canHandleRequest()
? response.renderCoreApp()
diff --git a/x-pack/plugins/security/server/routes/views/account_management.ts b/x-pack/plugins/security/server/routes/views/account_management.ts
index af49f325a25d2..4b3fbb78fed90 100644
--- a/x-pack/plugins/security/server/routes/views/account_management.ts
+++ b/x-pack/plugins/security/server/routes/views/account_management.ts
@@ -11,7 +11,8 @@ import type { RouteDefinitionParams } from '..';
* Defines routes required for the Account Management view.
*/
export function defineAccountManagementRoutes({ httpResources }: RouteDefinitionParams) {
- httpResources.register({ path: '/security/account', validate: false }, (context, req, res) =>
- res.renderCoreApp()
+ httpResources.register(
+ { path: '/security/account', validate: false, options: { excludeFromOAS: true } },
+ (context, req, res) => res.renderCoreApp()
);
}
diff --git a/x-pack/plugins/security/server/routes/views/capture_url.test.ts b/x-pack/plugins/security/server/routes/views/capture_url.test.ts
index 1893ad6c9cb5f..4496ab341b085 100644
--- a/x-pack/plugins/security/server/routes/views/capture_url.test.ts
+++ b/x-pack/plugins/security/server/routes/views/capture_url.test.ts
@@ -34,7 +34,7 @@ describe('Capture URL view routes', () => {
});
it('correctly defines route.', () => {
- expect(routeConfig.options).toEqual({ authRequired: false });
+ expect(routeConfig.options).toEqual({ authRequired: false, excludeFromOAS: true });
expect(routeConfig.validate).toEqual({
body: undefined,
diff --git a/x-pack/plugins/security/server/routes/views/capture_url.ts b/x-pack/plugins/security/server/routes/views/capture_url.ts
index 8eff92d78999d..394b799ca1f9d 100644
--- a/x-pack/plugins/security/server/routes/views/capture_url.ts
+++ b/x-pack/plugins/security/server/routes/views/capture_url.ts
@@ -19,7 +19,7 @@ export function defineCaptureURLRoutes({ httpResources }: RouteDefinitionParams)
validate: {
query: schema.object({ next: schema.maybe(schema.string()) }, { unknowns: 'ignore' }),
},
- options: { authRequired: false },
+ options: { authRequired: false, excludeFromOAS: true },
},
(context, request, response) => response.renderAnonymousCoreApp()
);
diff --git a/x-pack/plugins/security/server/routes/views/logged_out.test.ts b/x-pack/plugins/security/server/routes/views/logged_out.test.ts
index 850a533e3d93a..9aecb39750b1b 100644
--- a/x-pack/plugins/security/server/routes/views/logged_out.test.ts
+++ b/x-pack/plugins/security/server/routes/views/logged_out.test.ts
@@ -35,7 +35,7 @@ describe('LoggedOut view routes', () => {
});
it('correctly defines route.', () => {
- expect(routeConfig.options).toEqual({ authRequired: false });
+ expect(routeConfig.options).toEqual({ authRequired: false, excludeFromOAS: true });
expect(routeConfig.validate).toBe(false);
});
diff --git a/x-pack/plugins/security/server/routes/views/logged_out.ts b/x-pack/plugins/security/server/routes/views/logged_out.ts
index 360c0fb2c9b7c..66581f574def8 100644
--- a/x-pack/plugins/security/server/routes/views/logged_out.ts
+++ b/x-pack/plugins/security/server/routes/views/logged_out.ts
@@ -20,7 +20,7 @@ export function defineLoggedOutRoutes({
{
path: '/security/logged_out',
validate: false,
- options: { authRequired: false },
+ options: { authRequired: false, excludeFromOAS: true },
},
async (context, request, response) => {
// Authentication flow isn't triggered automatically for this route, so we should explicitly
diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts
index b19ef41ca9098..11797e20523e9 100644
--- a/x-pack/plugins/security/server/routes/views/login.test.ts
+++ b/x-pack/plugins/security/server/routes/views/login.test.ts
@@ -52,7 +52,7 @@ describe('Login view routes', () => {
});
it('correctly defines route.', () => {
- expect(routeConfig.options).toEqual({ authRequired: 'optional' });
+ expect(routeConfig.options).toEqual({ authRequired: 'optional', excludeFromOAS: true });
expect(routeConfig.validate).toEqual({
body: undefined,
diff --git a/x-pack/plugins/security/server/routes/views/login.ts b/x-pack/plugins/security/server/routes/views/login.ts
index 5d4468fcbba57..8cf8459d523b8 100644
--- a/x-pack/plugins/security/server/routes/views/login.ts
+++ b/x-pack/plugins/security/server/routes/views/login.ts
@@ -39,7 +39,7 @@ export function defineLoginRoutes({
{ unknowns: 'allow' }
),
},
- options: { authRequired: 'optional' },
+ options: { authRequired: 'optional', excludeFromOAS: true },
},
async (context, request, response) => {
// Default to true if license isn't available or it can't be resolved for some reason.
diff --git a/x-pack/plugins/security/server/routes/views/logout.ts b/x-pack/plugins/security/server/routes/views/logout.ts
index 3fb905ee10d37..d61f4e83083d2 100644
--- a/x-pack/plugins/security/server/routes/views/logout.ts
+++ b/x-pack/plugins/security/server/routes/views/logout.ts
@@ -12,7 +12,7 @@ import type { RouteDefinitionParams } from '..';
*/
export function defineLogoutRoutes({ httpResources }: RouteDefinitionParams) {
httpResources.register(
- { path: '/logout', validate: false, options: { authRequired: false } },
+ { path: '/logout', validate: false, options: { authRequired: false, excludeFromOAS: true } },
(context, request, response) => response.renderAnonymousCoreApp()
);
}
diff --git a/x-pack/plugins/security/server/routes/views/overwritten_session.ts b/x-pack/plugins/security/server/routes/views/overwritten_session.ts
index 115f7ea0a093f..4ab57f2cc9e72 100644
--- a/x-pack/plugins/security/server/routes/views/overwritten_session.ts
+++ b/x-pack/plugins/security/server/routes/views/overwritten_session.ts
@@ -12,7 +12,7 @@ import type { RouteDefinitionParams } from '..';
*/
export function defineOverwrittenSessionRoutes({ httpResources }: RouteDefinitionParams) {
httpResources.register(
- { path: '/security/overwritten_session', validate: false },
+ { path: '/security/overwritten_session', validate: false, options: { excludeFromOAS: true } },
(context, req, res) => res.renderCoreApp()
);
}
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 877214641dc1e..2fd83a4849a75 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -513,3 +513,8 @@ export const CASE_ATTACHMENT_ENDPOINT_TYPE_ID = 'endpoint' as const;
*/
export const MAX_MANUAL_RULE_RUN_LOOKBACK_WINDOW_DAYS = 90;
export const MAX_MANUAL_RULE_RUN_BULK_SIZE = 100;
+
+/*
+ * Whether it is a Jest environment
+ */
+export const JEST_ENVIRONMENT = typeof jest !== 'undefined';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
index b6df692dcabfd..3d50029f70315 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx
@@ -40,69 +40,67 @@ import { useStatefulRowRenderer } from './use_stateful_row_renderer';
* which focuses the current or next row, respectively.
* - A screen-reader-only message provides additional context and instruction
*/
-export const StatefulRowRenderer = ({
- ariaRowindex,
- containerRef,
- event,
- lastFocusedAriaColindex,
- rowRenderers,
- timelineId,
-}: {
- ariaRowindex: number;
- containerRef: React.MutableRefObject;
- event: TimelineItem;
- lastFocusedAriaColindex: number;
- rowRenderers: RowRenderer[];
- timelineId: string;
-}) => {
- const { focusOwnership, onFocus, onKeyDown, onOutsideClick } = useStatefulEventFocus({
+export const StatefulRowRenderer = React.memo(
+ ({
ariaRowindex,
- colindexAttribute: ARIA_COLINDEX_ATTRIBUTE,
containerRef,
+ event,
lastFocusedAriaColindex,
- onColumnFocused: noop,
- rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE,
- });
-
- const { rowRenderer } = useStatefulRowRenderer({
- data: event.ecs,
rowRenderers,
- });
-
- const content = useMemo(
- () =>
- rowRenderer && (
- // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
-
-
-
-
- {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}
-
-
-
- {rowRenderer.renderRow({
- data: event.ecs,
- isDraggable: true,
- scopeId: timelineId,
- })}
-
-
-
-
-
- ),
- [
+ timelineId,
+ }: {
+ ariaRowindex: number;
+ containerRef: React.MutableRefObject;
+ event: TimelineItem;
+ lastFocusedAriaColindex: number;
+ rowRenderers: RowRenderer[];
+ timelineId: string;
+ }) => {
+ const { focusOwnership, onFocus, onKeyDown, onOutsideClick } = useStatefulEventFocus({
ariaRowindex,
- event.ecs,
- focusOwnership,
- onFocus,
- onKeyDown,
- onOutsideClick,
- rowRenderer,
- timelineId,
- ]
- );
+ colindexAttribute: ARIA_COLINDEX_ATTRIBUTE,
+ containerRef,
+ lastFocusedAriaColindex,
+ onColumnFocused: noop,
+ rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE,
+ });
+
+ const { rowRenderer } = useStatefulRowRenderer({
+ data: event.ecs,
+ rowRenderers,
+ });
+
+ const row = useMemo(() => {
+ const result = rowRenderer?.renderRow({
+ data: event.ecs,
+ isDraggable: false,
+ scopeId: timelineId,
+ });
+ return result;
+ }, [rowRenderer, event.ecs, timelineId]);
+
+ const content = useMemo(
+ () =>
+ rowRenderer && (
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+
+
+
+
+ {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}
+
+
+ {row}
+
+
+
+
+ ),
+ [ariaRowindex, focusOwnership, onFocus, onKeyDown, onOutsideClick, rowRenderer, row]
+ );
+
+ return content;
+ }
+);
- return content;
-};
+StatefulRowRenderer.displayName = 'StatefulRowRenderer';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/use_stateful_row_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/use_stateful_row_renderer.ts
index 504cbe94a9102..7648c94288907 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/use_stateful_row_renderer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/use_stateful_row_renderer.ts
@@ -17,6 +17,7 @@ interface UseStatefulRowRendererArgs {
export function useStatefulRowRenderer(args: UseStatefulRowRendererArgs) {
const { data, rowRenderers } = args;
+
const rowRenderer = useMemo(() => getRowRenderer({ data, rowRenderers }), [data, rowRenderers]);
const result = useMemo(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx
index 662baa8e6b665..beeaadb7829c8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx
@@ -5,10 +5,11 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
+import React, { useMemo, useEffect } from 'react';
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import type { SortColumnTable } from '@kbn/securitysolution-data-table';
import type { TimelineItem } from '@kbn/timelines-plugin/common';
+import { JEST_ENVIRONMENT } from '../../../../../../common/constants';
import { useLicense } from '../../../../../common/hooks/use_license';
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
import { useSourcererDataView } from '../../../../../sourcerer/containers';
@@ -21,6 +22,7 @@ import { TimelineControlColumnCellRender } from '../../unified_components/data_t
import type { ColumnHeaderOptions } from '../../../../../../common/types';
import { useTimelineColumns } from './use_timeline_columns';
import type { UnifiedTimelineDataGridCellContext } from '../../types';
+import { useTimelineUnifiedDataTableContext } from '../../unified_components/data_table/use_timeline_unified_data_table_context';
interface UseTimelineControlColumnArgs {
columns: ColumnHeaderOptions[];
@@ -59,6 +61,58 @@ export const useTimelineControlColumn = ({
const ACTION_BUTTON_COUNT = isEnterprisePlus ? 6 : 5;
const { localColumns } = useTimelineColumns(columns);
+ const RowCellRender = useMemo(
+ () =>
+ function TimelineControlColumnCellRenderer(
+ props: EuiDataGridCellValueElementProps & UnifiedTimelineDataGridCellContext
+ ) {
+ const ctx = useTimelineUnifiedDataTableContext();
+
+ useEffect(() => {
+ props.setCellProps({
+ className:
+ ctx.expanded?.id === events[props.rowIndex]?._id
+ ? 'unifiedDataTable__cell--expanded'
+ : '',
+ });
+ });
+
+ /*
+ * In some cases, when number of events is updated
+ * but new table is not yet rendered it can result
+ * in the mismatch between the number of events v/s
+ * the number of rows in the table currently rendered.
+ *
+ * */
+ if ('rowIndex' in props && props.rowIndex >= events.length) return <>>;
+ return (
+
+ );
+ },
+ [events, timelineId, refetch, pinnedEventIds, eventIdToNoteIds, onToggleShowNotes]
+ );
+
// We need one less when the unified components are enabled because the document expand is provided by the unified data table
const UNIFIED_COMPONENTS_ACTION_BUTTON_COUNT = ACTION_BUTTON_COUNT - 1;
return useMemo(() => {
@@ -84,49 +138,7 @@ export const useTimelineControlColumn = ({
/>
);
},
- rowCellRender: (
- props: EuiDataGridCellValueElementProps & UnifiedTimelineDataGridCellContext
- ) => {
- /*
- * In some cases, when number of events is updated
- * but new table is not yet rendered it can result
- * in the mismatch between the number of events v/s
- * the number of rows in the table currently rendered.
- *
- * */
- if ('rowIndex' in props && props.rowIndex >= events.length) return <>>;
- props.setCellProps({
- className:
- props.expandedEventId === events[props.rowIndex]?._id
- ? 'unifiedDataTable__cell--expanded'
- : '',
- });
-
- return (
-
- );
- },
+ rowCellRender: JEST_ENVIRONMENT ? RowCellRender : React.memo(RowCellRender),
}));
} else {
return getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
@@ -142,11 +154,7 @@ export const useTimelineControlColumn = ({
sort,
activeTab,
timelineId,
- refetch,
- events,
- pinnedEventIds,
- eventIdToNoteIds,
- onToggleShowNotes,
ACTION_BUTTON_COUNT,
+ RowCellRender,
]);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap
index d54175194b748..d5a892b4e54ce 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/__snapshots__/custom_timeline_data_grid_body.test.tsx.snap
@@ -2,32 +2,35 @@
exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = `
.c0 {
- width: -webkit-fit-content;
- width: -moz-fit-content;
- width: fit-content;
- border-bottom: 1px solid 1px solid #343741;
-}
-
-.c0 . euiDataGridRowCell--controlColumn {
- height: auto;
- min-height: 34px;
+ width: 100%;
+ height: 100%;
+ border-bottom: 1px solid #343741;
}
.c0 .udt--customRow {
border-radius: 0;
padding: 6px;
- max-width: 1200px;
- width: 85vw;
+ max-width: 1000px;
}
-.c0 .euiCommentEvent__body {
- background-color: #1d1e24;
+.c0 .euiDataGridRowCell--lastColumn.euiDataGridRowCell--controlColumn .euiDataGridRowCell__content {
+ width: 1000px;
+ max-width: 1000px;
+ overflow-x: auto;
+ -webkit-scrollbar-width: thin;
+ -moz-scrollbar-width: thin;
+ -ms-scrollbar-width: thin;
+ scrollbar-width: thin;
+ -webkit-scroll-padding: 0 0 0 0,;
+ -moz-scroll-padding: 0 0 0 0,;
+ -ms-scroll-padding: 0 0 0 0,;
+ scroll-padding: 0 0 0 0,;
}
-.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--firstColumn,
-.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--lastColumn,
-.c0:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--controlColumn,
-.c0:has(.unifiedDataTable__cell--expanded) .udt--customRow {
+.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--firstColumn,
+.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--lastColumn,
+.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .euiDataGridRowCell--controlColumn,
+.c0 .euiDataGridRow:has(.unifiedDataTable__cell--expanded) .udt--customRow {
background-color: #2e2d25;
}
@@ -42,47 +45,76 @@ exports[`CustomTimelineDataGridBody should render exactly as snapshots 1`] = `
-
- Cell-0-0
-
-
- Cell-0-1
+
+
+
+
+
+
+ Cell-0-0
+
+
+ Cell-0-1
+
+
+ Cell-0-2
+
+
+
+ Cell-0-3
+
+
+
+
+
+
+
+ Cell-1-0
+
+
+ Cell-1-1
+
+
+ Cell-1-2
+
+
+
+ Cell-1-3
+
+
+
+
-
- Cell-0-2
-
-
-
- Cell-0-3
-
-
-
-
-
- Cell-1-0
-
-
- Cell-1-1
-
-
- Cell-1-2
-
-
-
- Cell-1-3
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx
index b6d7f52f2d92f..cfdb2b0d2dbf9 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.test.tsx
@@ -48,10 +48,10 @@ const defaultProps: CustomTimelineDataGridBodyProps = {
visibleColumns: mockVisibleColumns,
headerRow: <>>,
footerRow: null,
- gridWidth: 0,
+ gridWidth: 1000,
};
-const renderTestComponents = (props?: CustomTimelineDataGridBodyProps) => {
+const renderTestComponents = (props?: Partial
) => {
const finalProps = props ? { ...defaultProps, ...props } : defaultProps;
return render(
@@ -88,8 +88,15 @@ describe('CustomTimelineDataGridBody', () => {
(useStatefulRowRenderer as jest.Mock).mockReturnValueOnce({
canShowRowRenderer: true,
});
- const { getByText, queryByText } = renderTestComponents();
+ const { getByTestId, getByText, queryByText } = renderTestComponents();
+
+ expect(getByTestId('customGridRowsContainer')).toBeVisible();
expect(queryByText('Cell-0-3')).toBeFalsy();
expect(getByText('Cell-1-3')).toBeInTheDocument();
});
+
+ it('should not render grid if gridWidth is 0', () => {
+ const { queryByTestId } = renderTestComponents({ gridWidth: 0 });
+ expect(queryByTestId('customGridRowsContainer')).not.toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx
index 559dcbf10c4e6..d03958bba5b62 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/custom_timeline_data_grid_body.tsx
@@ -5,18 +5,24 @@
* 2.0.
*/
-import type { EuiDataGridCustomBodyProps } from '@elastic/eui';
+import type { EuiDataGridCustomBodyProps, EuiDataGridRowHeightsOptions } from '@elastic/eui';
import type { DataTableRecord } from '@kbn/discover-utils/types';
-import type { EuiTheme } from '@kbn/react-kibana-context-styled';
+import { type EuiTheme } from '@kbn/react-kibana-context-styled';
import type { TimelineItem } from '@kbn/timelines-plugin/common';
-import type { FC } from 'react';
-import React, { memo, useMemo } from 'react';
+import type { CSSProperties, FC, PropsWithChildren } from 'react';
+import React, { memo, useMemo, useState, useEffect, useRef, useCallback } from 'react';
import styled from 'styled-components';
+import { VariableSizeList } from 'react-window';
+import { EuiAutoSizer, useEuiTheme } from '@elastic/eui';
import type { RowRenderer } from '../../../../../../common/types';
import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants';
import { useStatefulRowRenderer } from '../../body/events/stateful_row_renderer/use_stateful_row_renderer';
import { getEventTypeRowClassName } from './get_event_type_row_classname';
+const defaultAutoHeight: EuiDataGridRowHeightsOptions = {
+ defaultHeight: 'auto',
+};
+
export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
rows: Array | undefined;
enabledRowRenderers: RowRenderer[];
@@ -24,9 +30,46 @@ export type CustomTimelineDataGridBodyProps = EuiDataGridCustomBodyProps & {
refetch?: () => void;
};
+const VirtualizedCustomDataGridContainer = styled.div<{
+ $maxWidth?: number;
+}>`
+ width: 100%;
+ height: 100%;
+ border-bottom: ${(props) => (props.theme as EuiTheme).eui.euiBorderThin};
+ .udt--customRow {
+ border-radius: 0;
+ padding: ${(props) => (props.theme as EuiTheme).eui.euiDataGridCellPaddingM};
+ max-width: ${(props) => props.$maxWidth}px;
+ }
+
+ .euiDataGridRowCell--lastColumn.euiDataGridRowCell--controlColumn .euiDataGridRowCell__content {
+ width: ${(props) => props.$maxWidth}px;
+ max-width: ${(props) => props.$maxWidth}px;
+ overflow-x: auto;
+ scrollbar-width: thin;
+ scroll-padding: 0 0 0 0,
+ }
+
+ .euiDataGridRow:has(.unifiedDataTable__cell--expanded) {
+ .euiDataGridRowCell--firstColumn,
+ .euiDataGridRowCell--lastColumn,
+ .euiDataGridRowCell--controlColumn,
+ .udt--customRow {
+ ${({ theme }) => `background-color: ${theme.eui.euiColorHighlight};`}
+ }
+ }
+ }
+`;
+
// THE DataGrid Row default is 34px, but we make ours 40 to account for our row actions
const DEFAULT_UDT_ROW_HEIGHT = 34;
+const SCROLLBAR_STYLE: CSSProperties = {
+ scrollbarWidth: 'thin',
+ scrollPadding: '0 0 0 0',
+ overflow: 'auto',
+};
+
/**
*
* In order to render the additional row with every event ( which displays the row-renderer, notes and notes editor)
@@ -44,40 +87,170 @@ export const CustomTimelineDataGridBody: FC = m
function CustomTimelineDataGridBody(props) {
const {
Cell,
- headerRow,
- footerRow,
visibleColumns,
visibleRowData,
rows,
rowHeight,
enabledRowRenderers,
refetch,
+ setCustomGridBodyProps,
+ headerRow,
+ footerRow,
+ gridWidth,
} = props;
+ const { euiTheme } = useEuiTheme();
+
+ // // Set custom props onto the grid body wrapper
+ const bodyRef = useRef(null);
+ useEffect(() => {
+ setCustomGridBodyProps({
+ ref: bodyRef,
+ style: {
+ width: '100%',
+ height: '100%',
+ overflowY: 'hidden',
+ scrollbarColor: `${euiTheme.colors.mediumShade} ${euiTheme.colors.lightestShade}`,
+ },
+ });
+ }, [setCustomGridBodyProps, euiTheme.colors.mediumShade, euiTheme.colors.lightestShade]);
+
const visibleRows = useMemo(
() => (rows ?? []).slice(visibleRowData.startRow, visibleRowData.endRow),
[rows, visibleRowData]
);
+ const listRef = useRef>(null);
+
+ const rowHeights = useRef([]);
+
+ const setRowHeight = useCallback((index: number, height: number) => {
+ if (rowHeights.current[index] === height) return;
+ listRef.current?.resetAfterIndex(index);
+
+ rowHeights.current[index] = height;
+ }, []);
+
+ const getRowHeight = useCallback((index: number) => {
+ return rowHeights.current[index] ?? 100;
+ }, []);
+
+ /*
+ *
+ * There is a difference between calculatedWidth & gridWidth
+ *
+ * gridWidth is the width of the grid as per the screen size
+ *
+ * calculatedWidth is the width of the grid that is calculated by EUI and represents
+ * the actual width of the grid based on the content of the grid. ( Sum of the width of all columns)
+ *
+ * For example, screensize can be variable but calculatedWidth can be much more than that
+ * with grid having a horizontal scrollbar
+ *
+ *
+ * */
+ const [calculatedWidth, setCalculatedWidth] = useState(gridWidth);
+
+ useEffect(() => {
+ /*
+ * Any time gridWidth(available screen size) is changed, we need to re-check
+ * to see if EUI has changed the width of the grid
+ *
+ */
+ if (!bodyRef) return;
+ const headerRowRef = bodyRef?.current?.querySelector('.euiDataGridHeader[role="row"]');
+ setCalculatedWidth((prev) =>
+ headerRowRef?.clientWidth && headerRowRef?.clientWidth !== prev
+ ? headerRowRef?.clientWidth
+ : prev
+ );
+ }, [gridWidth]);
+
+ const innerRowContainer = useMemo(() => {
+ const InnerComp = React.forwardRef<
+ HTMLDivElement,
+ PropsWithChildren<{ style: CSSProperties }>
+ >(({ children, style, ...rest }, ref) => {
+ return (
+ <>
+ {headerRow}
+
+ {children}
+
+
+ {footerRow}
+ >
+ );
+ });
+
+ InnerComp.displayName = 'InnerRowContainer';
+
+ return React.memo(InnerComp);
+ }, [headerRow, footerRow]);
+
return (
- <>
- {headerRow}
- {visibleRows.map((row, rowIndex) => {
- return (
-
- );
- })}
- {footerRow}
- >
+
+
+ {({ height }) => {
+ return (
+ <>
+ {
+ /**
+ * whenever timeline is minimized, VariableList is re-rendered which causes delay,
+ * so below code makes sure that grid is only rendered when gridWidth is not 0
+ */
+ gridWidth !== 0 && (
+ <>
+
+ {({ index, style }) => {
+ return (
+
+
+
+ );
+ }}
+
+ >
+ )
+ }
+ >
+ );
+ }}
+
+
);
}
);
@@ -85,41 +258,17 @@ export const CustomTimelineDataGridBody: FC = m
/**
*
* A Simple Wrapper component for displaying a custom grid row
+ * Generating CSS on this row puts a huge performance overhead on the grid as each row much styled individually.
+ * If possible, try to use the styles either in ../styles.tsx or in the parent component
*
*/
+
const CustomGridRow = styled.div.attrs<{
className?: string;
}>((props) => ({
className: `euiDataGridRow ${props.className ?? ''}`,
role: 'row',
-}))`
- width: fit-content;
- border-bottom: 1px solid ${(props) => (props.theme as EuiTheme).eui.euiBorderThin};
- . euiDataGridRowCell--controlColumn {
- height: ${(props: { $cssRowHeight: string }) => props.$cssRowHeight};
- min-height: ${DEFAULT_UDT_ROW_HEIGHT}px;
- }
- .udt--customRow {
- border-radius: 0;
- padding: ${(props) => (props.theme as EuiTheme).eui.euiDataGridCellPaddingM};
- max-width: ${(props) => (props.theme as EuiTheme).eui.euiPageDefaultMaxWidth};
- width: 85vw;
- }
-
- .euiCommentEvent__body {
- background-color: ${(props) => (props.theme as EuiTheme).eui.euiColorEmptyShade};
- }
-
- &:has(.unifiedDataTable__cell--expanded) {
- .euiDataGridRowCell--firstColumn,
- .euiDataGridRowCell--lastColumn,
- .euiDataGridRowCell--controlColumn,
- .udt--customRow {
- ${({ theme }) => `background-color: ${theme.eui.euiColorHighlight};`}
- }
- }
- }
-`;
+}))``;
/* below styles as per : https://eui.elastic.co/#/tabular-content/data-grid-advanced#custom-body-renderer */
const CustomGridRowCellWrapper = styled.div.attrs<{
@@ -138,6 +287,7 @@ const CustomGridRowCellWrapper = styled.div.attrs<{
type CustomTimelineDataGridSingleRowProps = {
rowData: DataTableRecord & TimelineItem;
rowIndex: number;
+ setRowHeight: (index: number, height: number) => void;
} & Pick<
CustomTimelineDataGridBodyProps,
'visibleColumns' | 'Cell' | 'enabledRowRenderers' | 'refetch' | 'rowHeight'
@@ -168,13 +318,24 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
visibleColumns,
Cell,
rowHeight: rowHeightMultiple = 0,
+ setRowHeight,
} = props;
+
const { canShowRowRenderer } = useStatefulRowRenderer({
data: rowData.ecs,
rowRenderers: enabledRowRenderers,
});
+ const rowRef = useRef(null);
+
+ useEffect(() => {
+ if (rowRef.current) {
+ setRowHeight(rowIndex, rowRef.current.offsetHeight);
+ }
+ }, [rowIndex, setRowHeight]);
+
const cssRowHeight: string = calculateRowHeightInPixels(rowHeightMultiple - 1);
+
/**
* removes the border between the actual row ( timelineEvent) and `TimelineEventDetail` row
* which renders the row-renderer, notes and notes editor
@@ -194,12 +355,11 @@ const CustomDataGridSingleRow = memo(function CustomDataGridSingleRow(
return (
{visibleColumns.map((column, colIndex) => {
- // Skip the expanded row cell - we'll render it manually outside of the flex wrapper
if (column.id !== TIMELINE_EVENT_DETAIL_ROW_ID) {
return (
|
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
index e4862fe8d72f6..875c147d6a700 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx
@@ -12,8 +12,13 @@ import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { UnifiedDataTableProps } from '@kbn/unified-data-table';
import { UnifiedDataTable, DataLoadingState } from '@kbn/unified-data-table';
import type { DataView } from '@kbn/data-views-plugin/public';
-import type { EuiDataGridCustomBodyProps, EuiDataGridProps } from '@elastic/eui';
+import type {
+ EuiDataGridControlColumn,
+ EuiDataGridCustomBodyProps,
+ EuiDataGridProps,
+} from '@elastic/eui';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { JEST_ENVIRONMENT } from '../../../../../../common/constants';
import { useOnExpandableFlyoutClose } from '../../../../../flyout/shared/hooks/use_on_expandable_flyout_close';
import { DocumentDetailsRightPanelKey } from '../../../../../flyout/document_details/shared/constants/panel_keys';
import { selectTimelineById } from '../../../../store/selectors';
@@ -43,7 +48,6 @@ import { transformTimelineItemToUnifiedRows } from '../utils';
import { TimelineEventDetailRow } from './timeline_event_detail_row';
import { CustomTimelineDataGridBody } from './custom_timeline_data_grid_body';
import { TIMELINE_EVENT_DETAIL_ROW_ID } from '../../body/constants';
-import type { UnifiedTimelineDataGridCellContext } from '../../types';
export const SAMPLE_SIZE_SETTING = 500;
const DataGridMemoized = React.memo(UnifiedDataTable);
@@ -288,6 +292,23 @@ export const TimelineDataTableComponent: React.FC = memo(
return rowRenderers.filter((rowRenderer) => !excludedRowRendererIds.includes(rowRenderer.id));
}, [excludedRowRendererIds, rowRenderers]);
+ const TimelineEventDetailRowRendererComp = useMemo(
+ () =>
+ function TimelineEventDetailRowRenderer(props) {
+ const { rowIndex, ...restProps } = props;
+ return (
+
+ );
+ },
+ [tableRows, timelineId, enabledRowRenderers]
+ );
+
/**
* Ref: https://eui.elastic.co/#/tabular-content/data-grid-advanced#custom-body-renderer
*/
@@ -295,31 +316,20 @@ export const TimelineDataTableComponent: React.FC = memo(
() => [
{
id: TIMELINE_EVENT_DETAIL_ROW_ID,
- // The header cell should be visually hidden, but available to screen readers
width: 0,
+ // The header cell should be visually hidden, but available to screen readers
headerCellRender: () => <>>,
headerCellProps: { className: 'euiScreenReaderOnly' },
// The footer cell can be hidden to both visual & SR users, as it does not contain meaningful information
footerCellProps: { style: { display: 'none' } },
- // When rendering this custom cell, we'll want to override
- // the automatic width/heights calculated by EuiDataGrid
- rowCellRender: (props) => {
- const { rowIndex, ...restProps } = props;
- return (
-
- );
- },
+ rowCellRender: JEST_ENVIRONMENT
+ ? TimelineEventDetailRowRendererComp
+ : React.memo(TimelineEventDetailRowRendererComp),
},
],
- [enabledRowRenderers, tableRows, timelineId]
+ [TimelineEventDetailRowRendererComp]
);
/**
@@ -352,12 +362,6 @@ export const TimelineDataTableComponent: React.FC = memo(
[tableRows, enabledRowRenderers, rowHeight, refetch]
);
- const cellContext: UnifiedTimelineDataGridCellContext = useMemo(() => {
- return {
- expandedEventId: expandedDoc?.id,
- };
- }, [expandedDoc]);
-
const finalRenderCustomBodyCallback = useMemo(() => {
return enabledRowRenderers.length > 0 ? renderCustomBodyCallback : undefined;
}, [enabledRowRenderers.length, renderCustomBodyCallback]);
@@ -419,7 +423,6 @@ export const TimelineDataTableComponent: React.FC = memo(
renderCustomGridBody={finalRenderCustomBodyCallback}
trailingControlColumns={finalTrailControlColumns}
externalControlColumns={leadingControlColumns}
- cellContext={cellContext}
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.test.tsx
index a4de0c7dfa318..92070ec1fada0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.test.tsx
@@ -18,7 +18,14 @@ const mockData = structuredClone(mockTimelineData);
const setCellPropsMock = jest.fn();
-jest.mock('../../body/events/stateful_row_renderer');
+jest.mock('../../body/events/stateful_row_renderer', () => {
+ return {
+ StatefulRowRenderer: jest.fn(),
+ };
+});
+
+const StatefulRowRendererMock = StatefulRowRenderer as unknown as jest.Mock;
+
jest.mock('./use_timeline_unified_data_table_context');
const renderTestComponent = (props: Partial = {}) => {
@@ -44,7 +51,7 @@ const renderTestComponent = (props: Partial = {}) =
describe('TimelineEventDetailRow', () => {
beforeEach(() => {
- (StatefulRowRenderer as jest.Mock).mockReturnValue({'Test Row Renderer'}
);
+ StatefulRowRendererMock.mockReturnValue({'Test Row Renderer'}
);
(useTimelineUnifiedDataTableContext as jest.Mock).mockReturnValue({
expanded: { id: undefined },
@@ -60,7 +67,7 @@ describe('TimelineEventDetailRow', () => {
expect(setCellPropsMock).toHaveBeenCalledWith({
className: '',
- style: { width: '100%', height: 'auto' },
+ style: { width: '100%', height: undefined, overflowX: 'auto' },
});
expect(getByText('Test Row Renderer')).toBeVisible();
@@ -82,7 +89,7 @@ describe('TimelineEventDetailRow', () => {
expect(setCellPropsMock).toHaveBeenCalledWith({
className: 'unifiedDataTable__cell--expanded',
- style: { width: '100%', height: 'auto' },
+ style: { width: '100%', height: undefined, overflowX: 'auto' },
});
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.tsx
index 72a33af797210..9a3bc97254962 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/timeline_event_detail_row.tsx
@@ -60,7 +60,7 @@ export const TimelineEventDetailRow: React.FC = mem
useEffect(() => {
setCellProps?.({
className: ctx.expanded?.id === event._id ? 'unifiedDataTable__cell--expanded' : '',
- style: { width: '100%', height: 'auto' },
+ style: { width: '100%', height: undefined, overflowX: 'auto' },
});
}, [ctx.expanded?.id, setCellProps, rowIndex, event._id]);
@@ -72,7 +72,7 @@ export const TimelineEventDetailRow: React.FC = mem
alignItems="center"
data-test-subj={`timeline-row-renderer-${rowIndex}`}
>
-
+
+ `scrollbar-color: ${theme.eui.euiColorMediumShade} ${theme.eui.euiColorLightShade}`};
+ }
+
.udtTimeline [data-gridcell-column-id|='select'] {
border-right: none;
}
@@ -182,6 +187,10 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = ''
align-items: baseline;
}
+ .euiDataGrid__customRenderBody {
+ scrollbar-color: transparent !important;
+ }
+
${leadingActionsColumnStyles}
`;
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts
index 4156ea1dbd4ea..8079e54ac9ba6 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts
@@ -18,10 +18,11 @@ import type { AppClient } from '../../..';
describe('EntityStoreDataClient', () => {
const mockSavedObjectClient = savedObjectsClientMock.create();
- const esClientMock = elasticsearchServiceMock.createScopedClusterClient().asInternalUser;
+ const clusterClientMock = elasticsearchServiceMock.createScopedClusterClient();
+ const esClientMock = clusterClientMock.asCurrentUser;
const loggerMock = loggingSystemMock.createLogger();
const dataClient = new EntityStoreDataClient({
- esClient: esClientMock,
+ clusterClient: clusterClientMock,
logger: loggerMock,
namespace: 'default',
soClient: mockSavedObjectClient,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
index d2e21a1d10903..5b1acaa433cd0 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
@@ -10,6 +10,7 @@ import type {
ElasticsearchClient,
SavedObjectsClientContract,
AuditLogger,
+ IScopedClusterClient,
} from '@kbn/core/server';
import { EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
import type { SortOrder } from '@elastic/elasticsearch/lib/api/types';
@@ -55,7 +56,7 @@ import {
interface EntityStoreClientOpts {
logger: Logger;
- esClient: ElasticsearchClient;
+ clusterClient: IScopedClusterClient;
namespace: string;
soClient: SavedObjectsClientContract;
taskManager?: TaskManagerStartContract;
@@ -79,12 +80,14 @@ export class EntityStoreDataClient {
private assetCriticalityMigrationClient: AssetCriticalityEcsMigrationClient;
private entityClient: EntityClient;
private riskScoreDataClient: RiskScoreDataClient;
+ private esClient: ElasticsearchClient;
constructor(private readonly options: EntityStoreClientOpts) {
- const { esClient, logger, soClient, auditLogger, kibanaVersion, namespace } = options;
+ const { clusterClient, logger, soClient, auditLogger, kibanaVersion, namespace } = options;
+ this.esClient = clusterClient.asCurrentUser;
this.entityClient = new EntityClient({
- esClient,
+ clusterClient,
soClient,
logger,
});
@@ -95,14 +98,14 @@ export class EntityStoreDataClient {
});
this.assetCriticalityMigrationClient = new AssetCriticalityEcsMigrationClient({
- esClient,
+ esClient: this.esClient,
logger,
auditLogger,
});
this.riskScoreDataClient = new RiskScoreDataClient({
soClient,
- esClient,
+ esClient: this.esClient,
logger,
namespace,
kibanaVersion,
@@ -165,7 +168,7 @@ export class EntityStoreDataClient {
filter: string,
pipelineDebugMode: boolean
) {
- const { esClient, logger, namespace, appClient, dataViewsService } = this.options;
+ const { logger, namespace, appClient, dataViewsService } = this.options;
const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService);
const unitedDefinition = getUnitedEntityDefinition({
@@ -200,12 +203,12 @@ export class EntityStoreDataClient {
// this is because the enrich policy will fail if the index does not exist with the correct fields
await createEntityIndexComponentTemplate({
unitedDefinition,
- esClient,
+ esClient: this.esClient,
});
debugLog(`Created entity index component template`);
await createEntityIndex({
entityType,
- esClient,
+ esClient: this.esClient,
namespace,
logger,
});
@@ -215,12 +218,12 @@ export class EntityStoreDataClient {
// this is because the pipeline will fail if the enrich index does not exist
await createFieldRetentionEnrichPolicy({
unitedDefinition,
- esClient,
+ esClient: this.esClient,
});
debugLog(`Created field retention enrich policy`);
await executeFieldRetentionEnrichPolicy({
unitedDefinition,
- esClient,
+ esClient: this.esClient,
logger,
});
debugLog(`Executed field retention enrich policy`);
@@ -228,7 +231,7 @@ export class EntityStoreDataClient {
debugMode: pipelineDebugMode,
unitedDefinition,
logger,
- esClient,
+ esClient: this.esClient,
});
debugLog(`Created @platform pipeline`);
@@ -325,8 +328,9 @@ export class EntityStoreDataClient {
taskManager: TaskManagerStartContract,
options = { deleteData: false, deleteEngine: true }
) {
- const { namespace, logger, esClient, appClient, dataViewsService } = this.options;
+ const { namespace, logger, appClient, dataViewsService } = this.options;
const { deleteData, deleteEngine } = options;
+
const descriptor = await this.engineClient.maybeGet(entityType);
const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService);
const unitedDefinition = getUnitedEntityDefinition({
@@ -348,22 +352,22 @@ export class EntityStoreDataClient {
}
await deleteEntityIndexComponentTemplate({
unitedDefinition,
- esClient,
+ esClient: this.esClient,
});
await deletePlatformPipeline({
unitedDefinition,
logger,
- esClient,
+ esClient: this.esClient,
});
await deleteFieldRetentionEnrichPolicy({
unitedDefinition,
- esClient,
+ esClient: this.esClient,
});
if (deleteData) {
await deleteEntityIndex({
entityType,
- esClient,
+ esClient: this.esClient,
namespace,
logger,
});
@@ -402,7 +406,7 @@ export class EntityStoreDataClient {
const sort = sortField ? [{ [sortField]: sortOrder }] : undefined;
const query = filterQuery ? JSON.parse(filterQuery) : undefined;
- const response = await this.options.esClient.search({
+ const response = await this.esClient.search({
index,
query,
size: Math.min(perPage, MAX_SEARCH_RESPONSE_SIZE),
diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts
index 0782fa25c71eb..d2bd579dc6b03 100644
--- a/x-pack/plugins/security_solution/server/request_context_factory.ts
+++ b/x-pack/plugins/security_solution/server/request_context_factory.ts
@@ -199,14 +199,14 @@ export class RequestContextFactory implements IRequestContextFactory {
})
),
getEntityStoreDataClient: memoize(() => {
- const esClient = coreContext.elasticsearch.client.asCurrentUser;
+ const clusterClient = coreContext.elasticsearch.client;
const logger = options.logger;
const soClient = coreContext.savedObjects.client;
return new EntityStoreDataClient({
namespace: getSpaceId(),
+ clusterClient,
dataViewsService,
appClient: getAppClient(),
- esClient,
logger,
soClient,
taskManager: startPlugins.taskManager,
diff --git a/x-pack/plugins/spaces/server/routes/views/index.test.ts b/x-pack/plugins/spaces/server/routes/views/index.test.ts
index b87bfe86c022a..e42f2dcf42eaf 100644
--- a/x-pack/plugins/spaces/server/routes/views/index.test.ts
+++ b/x-pack/plugins/spaces/server/routes/views/index.test.ts
@@ -59,7 +59,7 @@ describe('Space Selector view routes', () => {
});
it('correctly defines route.', () => {
- expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.options).toEqual({ excludeFromOAS: true });
expect(routeConfig.validate).toBe(false);
});
diff --git a/x-pack/plugins/spaces/server/routes/views/index.ts b/x-pack/plugins/spaces/server/routes/views/index.ts
index ab06b17374f13..f21a665e35525 100644
--- a/x-pack/plugins/spaces/server/routes/views/index.ts
+++ b/x-pack/plugins/spaces/server/routes/views/index.ts
@@ -20,7 +20,7 @@ export interface ViewRouteDeps {
export function initSpacesViewsRoutes(deps: ViewRouteDeps) {
deps.httpResources.register(
- { path: '/spaces/space_selector', validate: false },
+ { path: '/spaces/space_selector', validate: false, options: { excludeFromOAS: true } },
(context, request, response) => response.renderCoreApp()
);
@@ -32,6 +32,7 @@ export function initSpacesViewsRoutes(deps: ViewRouteDeps) {
schema.object({ next: schema.maybe(schema.string()) }, { unknowns: 'ignore' })
),
},
+ options: { excludeFromOAS: true },
},
async (context, request, response) => {
try {
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index d2c35721fdddb..6d57eec4f3b99 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -20332,7 +20332,6 @@
"xpack.fleet.preconfiguration.packageMissingError": "Impossible d'ajouter [{agentPolicyName}]. [{pkgName}] n'est pas installé. Veuillez ajouter [{pkgName}] à [{packagesConfigValue}] ou le retirer de [{packagePolicyName}].",
"xpack.fleet.preconfiguration.packageRejectedError": "Impossible d'ajouter [{agentPolicyName}]. [{pkgName}] n'a pas pu être installé en raison d’une erreur : [{errorMessage}].",
"xpack.fleet.preconfiguration.policyDeleted": "La politique préconfigurée {id} a été supprimée ; ignorer la création",
- "xpack.fleet.preconfiguration.support_agentless": "`supports_agentless` n'est autorisé que dans les environnements sans serveur prenant en charge la fonctionnalité sans agent",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "La balise n’a pas pu être renommée",
"xpack.fleet.renameAgentTags.successNotificationTitle": "Balise renommée",
"xpack.fleet.requestDiagnostics.calloutText": "Les fichiers de diagnostics sont stockés dans Elasticsearch, et ils peuvent donc engendrer des coûts de stockage. Par défaut, les fichiers sont périodiquement supprimés via une stratégie ILM.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index bc4f0b3f6cf1a..97e97f1e97c51 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -20082,7 +20082,6 @@
"xpack.fleet.preconfiguration.packageMissingError": "[{agentPolicyName}]を追加できませんでした。[{pkgName}]がインストールされていません。[{pkgName}]を[{packagesConfigValue}]に追加するか、[{packagePolicyName}]から削除してください。",
"xpack.fleet.preconfiguration.packageRejectedError": "[{agentPolicyName}]を追加できませんでした。エラーのため、[{pkgName}]をインストールできませんでした:[{errorMessage}]",
"xpack.fleet.preconfiguration.policyDeleted": "構成済みのポリシー{id}が削除されました。作成をスキップしています",
- "xpack.fleet.preconfiguration.support_agentless": "supports_agentlessは、エージェントレス機能をサポートするサーバーレス環境でのみ許可されます",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "タグ名の変更が失敗しました",
"xpack.fleet.renameAgentTags.successNotificationTitle": "タグ名が変更されました",
"xpack.fleet.requestDiagnostics.calloutText": "診断ファイルはElasticsearchに保存されるため、ストレージコストが発生する可能性があります。デフォルトでは、ILMポリシーによって、ファイルが定期的に削除されます。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index e018909babf64..7194f977d4787 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -20112,7 +20112,6 @@
"xpack.fleet.preconfiguration.packageMissingError": "无法添加 [{agentPolicyName}]。[{pkgName}] 未安装,请将 [{pkgName}] 添加到 [{packagesConfigValue}] 或将其从 [{packagePolicyName}] 中移除。",
"xpack.fleet.preconfiguration.packageRejectedError": "无法添加 [{agentPolicyName}]。无法安装 [{pkgName}],因为出现错误:[{errorMessage}]",
"xpack.fleet.preconfiguration.policyDeleted": "预配置的策略 {id} 已删除;将跳过创建",
- "xpack.fleet.preconfiguration.support_agentless": "只有支持无代理功能的无服务器环境才允许使用 `supports_agentless`",
"xpack.fleet.renameAgentTags.errorNotificationTitle": "标签重命名失败",
"xpack.fleet.renameAgentTags.successNotificationTitle": "标签已重命名",
"xpack.fleet.requestDiagnostics.calloutText": "诊断文件存储在 Elasticsearch 中,因此可能产生存储成本。默认情况下,会通过 ILM 策略定期删除文件。",
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts
index 1ef7b170a4f0d..fcf0f2d84e755 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts
@@ -246,12 +246,12 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts
index 2268e379f441a..d41f8f1fcad71 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts
@@ -236,12 +236,12 @@ export default function jiraTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/opsgenie.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/opsgenie.ts
index bd315edfb0459..0c5f52862b9de 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/opsgenie.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/opsgenie.ts
@@ -169,12 +169,12 @@ export default function opsgenieTest({ getService }: FtrProviderContext) {
});
expect(200);
- expect(Object.keys(body)).to.eql([
- 'status',
+ expect(Object.keys(body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(body.connector_id).to.eql(opsgenieActionId);
expect(body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/resilient.ts
index 6dfb420463e9f..232668c24749c 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/resilient.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/resilient.ts
@@ -230,12 +230,12 @@ export default function resilientTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(resilientActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itom.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itom.ts
index 0f1748db4f5ef..c189580951495 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itom.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itom.ts
@@ -416,12 +416,12 @@ export default function serviceNowITOMTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts
index bc0f48f15caf5..1f4f01db068d9 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts
@@ -452,12 +452,12 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts
index 717a44a406712..527ea53bbd1d5 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts
@@ -465,12 +465,12 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/swimlane.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/swimlane.ts
index 4d91fdddf80dd..93c2e4bc973af 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/swimlane.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/swimlane.ts
@@ -327,12 +327,12 @@ export default function swimlaneTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
- expect(Object.keys(resp.body)).to.eql([
- 'status',
+ expect(Object.keys(resp.body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/tines.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/tines.ts
index 04971990f879e..25b3b4b35cc76 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/tines.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/tines.ts
@@ -188,12 +188,12 @@ export default function tinesTest({ getService }: FtrProviderContext) {
});
expect(200);
- expect(Object.keys(body)).to.eql([
- 'status',
+ expect(Object.keys(body).sort()).to.eql([
+ 'connector_id',
+ 'errorSource',
'message',
'retry',
- 'errorSource',
- 'connector_id',
+ 'status',
]);
expect(body.connector_id).to.eql(tinesActionId);
expect(body.status).to.eql('error');
diff --git a/x-pack/test/api_integration/apis/entity_manager/definitions.ts b/x-pack/test/api_integration/apis/entity_manager/definitions.ts
index b51a26ad7b5ad..a1fdab08ff42a 100644
--- a/x-pack/test/api_integration/apis/entity_manager/definitions.ts
+++ b/x-pack/test/api_integration/apis/entity_manager/definitions.ts
@@ -154,7 +154,6 @@ export default function ({ getService }: FtrProviderContext) {
after(async () => {
await esDeleteAllIndices(dataForgeIndices);
- await uninstallDefinition(supertest, { id: mockDefinition.id, deleteData: true });
await cleanup({ client: esClient, config: dataForgeConfig, logger });
});
@@ -171,6 +170,13 @@ export default function ({ getService }: FtrProviderContext) {
const parsedSample = entityLatestSchema.safeParse(sample.hits.hits[0]._source);
expect(parsedSample.success).to.be(true);
});
+
+ it('should delete entities data when specified', async () => {
+ const index = generateLatestIndexName(mockDefinition);
+ expect(await esClient.indices.exists({ index })).to.be(true);
+ await uninstallDefinition(supertest, { id: mockDefinition.id, deleteData: true });
+ expect(await esClient.indices.exists({ index })).to.be(false);
+ });
});
});
}
diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts
index 250fdb07b7132..e556db2e09a28 100644
--- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts
+++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts
@@ -23,9 +23,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const isServerless = config.get('serverless');
const expectedConsumer = isServerless ? 'observability' : 'slo';
- describe('Burn rate rule', function () {
- // see details: https://github.com/elastic/kibana/issues/196252
- this.tags(['failsOnMKI']);
+ describe('Burn rate rule', () => {
const RULE_TYPE_ID = 'slo.rules.burnRate';
const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*';
const RULE_ALERT_INDEX = '.alerts-observability.slo.alerts-default';
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
index 3c39bd235bf97..121bb753e434b 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
@@ -94,7 +94,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await rules.common.cancelRuleCreation();
}
- describe('create alert', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/196153
+ describe.skip('create alert', function () {
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
before(async () => {
await esArchiver.load(
diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json
index 1649c79f52a7d..18c4eba5fe79f 100644
--- a/x-pack/test/security_solution_api_integration/package.json
+++ b/x-pack/test/security_solution_api_integration/package.json
@@ -39,6 +39,10 @@
"initialize-server:edr-workflows": "node ./scripts/index.js server edr_workflows trial_license_complete_tier",
"run-tests:edr-workflows": "node ./scripts/index.js runner edr_workflows trial_license_complete_tier",
+
+ "initialize-server:investigations:basic_essentials": "node scripts/index.js server investigation basic_license_essentials_tier",
+ "run-tests:investigations:basic_essentials": "node scripts/index.js runner investigation basic_license_essentials_tier",
+
"initialize-server:investigations": "node scripts/index.js server investigation trial_license_complete_tier",
"run-tests:investigations": "node scripts/index.js runner investigation trial_license_complete_tier",
@@ -377,6 +381,14 @@
"investigations:timeline:server:ess": "npm run initialize-server:investigations timeline ess",
"investigations:timeline:runner:ess": "npm run run-tests:investigations timeline ess essEnv",
+
+ "investigations:basic:timeline:server:ess": "npm run initialize-server:investigations:basic_essentials timeline ess",
+ "investigations:basic:timeline:server:serverless": "npm run initialize-server:investigations:basic_essentials timeline serverless",
+ "investigations:basic:timeline:runner:ess": "npm run run-tests:investigations:basic_essentials timeline ess essEnv",
+ "investigations:basic:timeline:runner:serverless": "npm run run-tests:investigations:basic_essentials timeline serverless serverlessEnv",
+ "investigations:basic:timeline:runner:qa:serverless": "npm run run-tests:investigations:basic_essentials timeline serverless qaPeriodicEnv",
+ "investigations:basic:timeline:runner:qa:serverless:release": "npm run run-tests:investigations:basic_essentials timeline serverless qaEnv",
+
"investigations:saved-objects:server:serverless": "npm run initialize-server:investigations saved_objects serverless",
"investigations:saved-objects:runner:serverless": "npm run run-tests:investigations saved_objects serverless serverlessEnv",
"investigations:saved-objects:runner:qa:serverless": "npm run run-tests:investigations saved_objects serverless qaPeriodicEnv",
@@ -384,6 +396,13 @@
"investigations:saved-objects:server:ess": "npm run initialize-server:investigations saved_objects ess",
"investigations:saved-objects:runner:ess": "npm run run-tests:investigations saved_objects ess essEnv",
+ "investigations:basic:saved-objects:server:serverless": "npm run initialize-server:investigations:basic_essentials saved_objects serverless",
+ "investigations:basic:saved-objects:runner:serverless": "npm run run-tests:investigations:basic_essentials saved_objects serverless serverlessEnv",
+ "investigations:basic:saved-objects:runner:qa:serverless": "npm run run-tests:investigations:basic_essentials saved_objects serverless qaPeriodicEnv",
+ "investigations:basic:saved-objects:runner:qa:serverless:release": "npm run run-tests:investigations:basic_essentials saved_objects serverless qaEnv",
+ "investigations:basic:saved-objects:server:ess": "npm run initialize-server:investigations:basic_essentials saved_objects ess",
+ "investigations:basic:saved-objects:runner:ess": "npm run run-tests:investigations:basic_essentials saved_objects ess essEnv",
+
"explore:hosts:server:serverless": "npm run intialize-server:explore hosts serverless",
"explore:hosts:runner:serverless": "npm run run-tests:explore hosts serverless serverlessEnv",
"explore:hosts:runner:qa:serverless": "npm run run-tests:explore hosts serverless qaPeriodicEnv",
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts
index 3e3eb594cc0bc..0fb8644f5e93c 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts
@@ -47,7 +47,9 @@ export default ({ getService }: FtrProviderContext): void => {
const createWebHookConnector = () => createConnector(getWebHookAction());
// Failing: See https://github.com/elastic/kibana/issues/173804
- describe('@ess perform_bulk_action - ESS specific logic', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196470
+ // Failing: See https://github.com/elastic/kibana/issues/196462
+ describe.skip('@ess perform_bulk_action - ESS specific logic', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
index c10144aec0342..a7d32767f50ce 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
@@ -14,7 +14,8 @@ export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const utils = EntityStoreUtils(getService);
- describe('@ess @skipInServerlessMKI Entity Store Engine APIs', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196526
+ describe.skip('@ess @skipInServerlessMKI Entity Store Engine APIs', () => {
const dataView = dataViewRouteHelpersFactory(supertest);
before(async () => {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
index de949730d3d10..481f7aa4056f6 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
@@ -18,7 +18,8 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
const supertest = getService('supertest');
const utils = EntityStoreUtils(getService, namespace);
- describe('@ess Entity Store Engine APIs in non-default space', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196546
+ describe.skip('@ess Entity Store Engine APIs in non-default space', () => {
const dataView = dataViewRouteHelpersFactory(supertest, namespace);
before(async () => {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts
index 3224caa24d5e2..dd1fe34cd050a 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts
@@ -30,7 +30,8 @@ export default ({ getService }: FtrProviderContext) => {
const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest);
const log = getService('log');
- describe('@ess @serverless @serverlessQA init_and_status_apis', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196319
+ describe.skip('@ess @serverless @serverlessQA init_and_status_apis', () => {
before(async () => {
await riskEngineRoutes.cleanUp();
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/ess.config.ts
similarity index 79%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/ess.config.ts
index ad8b3a9ddcd39..d4a5b5f5a80c8 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/ess.config.ts
@@ -8,7 +8,7 @@
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
- require.resolve('../../../../../config/ess/config.base.trial')
+ require.resolve('../../../../../config/ess/config.base.basic')
);
return {
@@ -20,9 +20,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
],
},
- testFiles: [require.resolve('../tests/trial')],
+ testFiles: [require.resolve('../../tests')],
junit: {
- reportName: 'Timeline Integration Tests - ESS Env - Trial License',
+ reportName: 'Saved Objects Integration Tests - ESS Env - Basic License',
},
};
}
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts
new file mode 100644
index 0000000000000..5e70e9b7717d5
--- /dev/null
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { createTestConfig } from '../../../../../config/serverless/config.base';
+
+export default createTestConfig({
+ kbnTestServerArgs: [
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
+ `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([
+ { product_line: 'security', product_tier: 'essentials' },
+ { product_line: 'endpoint', product_tier: 'essentials' },
+ { product_line: 'cloud', product_tier: 'essentials' },
+ ])}`,
+ ],
+ testFiles: [require.resolve('../../tests')],
+ junit: {
+ reportName: 'Saved Objects Integration Tests - Serverless Env - Complete Tier',
+ },
+});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/draft_timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/draft_timeline.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/helpers.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/helpers.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/index.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts
similarity index 99%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts
index 5d1fefadb2f65..027c0a20262a8 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/notes.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts
@@ -15,7 +15,8 @@ export default function ({ getService }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const supertest = getService('supertest');
- describe('Note - Saved Objects', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/196492
+ describe.skip('Note - Saved Objects', () => {
const es = getService('es');
before(() => kibanaServer.savedObjects.cleanStandardList());
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/pinned_events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/pinned_events.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts
index 4c96f07342a58..80f9327a0c19e 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts
@@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
],
},
- testFiles: [require.resolve('..')],
+ testFiles: [require.resolve('../../tests')],
junit: {
reportName: 'Saved Objects Integration Tests - ESS Env - Basic License',
},
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts
index a5d28b90c8dc9..2bb0168b6e8ad 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts
@@ -16,7 +16,7 @@ export default createTestConfig({
{ product_line: 'cloud', product_tier: 'complete' },
])}`,
],
- testFiles: [require.resolve('..')],
+ testFiles: [require.resolve('../../tests')],
junit: {
reportName: 'Saved Objects Integration Tests - Serverless Env - Complete Tier',
},
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/ess.config.ts
similarity index 94%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/ess.config.ts
index 279b9a1a2ed57..a1bcb8a145ad6 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/ess.config.ts
@@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
],
},
- testFiles: [require.resolve('../tests/basic')],
+ testFiles: [require.resolve('../../tests')],
junit: {
reportName: 'Timeline Integration Tests - ESS Env - Basic License',
},
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts
new file mode 100644
index 0000000000000..2e5fa848fb16b
--- /dev/null
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { createTestConfig } from '../../../../../config/serverless/config.base';
+
+export default createTestConfig({
+ kbnTestServerArgs: [
+ `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
+ `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([
+ { product_line: 'security', product_tier: 'essentials' },
+ { product_line: 'endpoint', product_tier: 'essentials' },
+ { product_line: 'cloud', product_tier: 'essentials' },
+ ])}`,
+ ],
+ testFiles: [require.resolve('../../tests')],
+ junit: {
+ reportName: 'Timeline Integration Tests - Serverless Env - Essentials Tier',
+ },
+});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/mocks/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/mocks/timeline_details.ts
similarity index 100%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/mocks/timeline_details.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/mocks/timeline_details.ts
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/events.ts
deleted file mode 100644
index a694a4cd170da..0000000000000
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/events.ts
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { JsonObject } from '@kbn/utility-types';
-import expect from '@kbn/expect';
-import { ALERT_UUID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
-
-import { TimelineEdges, TimelineNonEcsData } from '@kbn/timelines-plugin/common';
-import {
- Direction,
- TimelineEventsQueries,
-} from '@kbn/security-solution-plugin/common/search_strategy';
-import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
-import { User } from '../../../../../../../rule_registry/common/lib/authentication/types';
-import { getSpaceUrlPrefix } from '../../../../../../../rule_registry/common/lib/authentication/spaces';
-
-import {
- superUser,
- globalRead,
- obsOnly,
- obsOnlyRead,
- obsSec,
- obsSecRead,
- secOnly,
- secOnlyRead,
- secOnlySpace2,
- secOnlyReadSpace2,
- obsSecAllSpace2,
- obsSecReadSpace2,
- obsOnlySpace2,
- obsOnlyReadSpace2,
- obsOnlySpacesAll,
- obsSecSpacesAll,
- secOnlySpacesAll,
- noKibanaPrivileges,
-} from '../../../../../../../rule_registry/common/lib/authentication/users';
-import type { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-
-interface TestCase {
- /** The space where the alert exists */
- space?: string;
- /** The ID of the solution for which to get alerts */
- featureIds: string[];
- /** The total alerts expected to be returned */
- expectedNumberAlerts: number;
- /** body to be posted */
- body: JsonObject;
- /** Authorized users */
- authorizedUsers: User[];
- /** Unauthorized users */
- unauthorizedUsers: User[];
- /** Users who are authorized for one, but not all of the alert solutions being queried */
- usersWithoutAllPrivileges?: User[];
-}
-
-const TO = '3000-01-01T00:00:00.000Z';
-const FROM = '2000-01-01T00:00:00.000Z';
-const TEST_URL = '/internal/search/timelineSearchStrategy/';
-const SPACE_1 = 'space1';
-const SPACE_2 = 'space2';
-
-export default ({ getService }: FtrProviderContextWithSpaces) => {
- const esArchiver = getService('esArchiver');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
- const getPostBody = (): JsonObject => ({
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_UUID, 'event.kind'],
- fields: [],
- filterQuery: {
- bool: {
- filter: [
- {
- match_all: {},
- },
- ],
- },
- },
- pagination: {
- activePage: 0,
- querySize: 25,
- },
- language: 'kuery',
- sort: [
- {
- field: '@timestamp',
- direction: Direction.desc,
- type: 'number',
- },
- ],
- timerange: {
- from: FROM,
- to: TO,
- interval: '12h',
- },
- });
-
- // TODO: Fix or update the tests
- describe.skip('Timeline - Events', () => {
- before(async () => {
- await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
- });
- after(async () => {
- await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
- });
-
- function addTests({
- space,
- authorizedUsers,
- usersWithoutAllPrivileges,
- unauthorizedUsers,
- body,
- featureIds,
- expectedNumberAlerts,
- }: TestCase) {
- authorizedUsers.forEach(({ username, password }) => {
- it(`${username} should be able to view alerts from "${featureIds.join(',')}" ${
- space != null ? `in space ${space}` : 'when no space specified'
- }`, async () => {
- // This will be flake until it uses the bsearch service, but these tests aren't operational. Once you do make this operational
- // use const bsearch = getService('bsearch');
- const resp = await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(space)}${TEST_URL}`)
- .auth(username, password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({ ...body })
- .expect(200);
-
- const timeline = resp.body;
-
- expect(
- timeline.edges.every((hit: TimelineEdges) => {
- const data: TimelineNonEcsData[] = hit.node.data;
- return data.some(({ field, value }) => {
- return (
- field === ALERT_RULE_CONSUMER && featureIds.includes((value && value[0]) ?? '')
- );
- });
- })
- ).to.equal(true);
- expect(timeline.totalCount).to.be(expectedNumberAlerts);
- });
- });
-
- if (usersWithoutAllPrivileges != null) {
- usersWithoutAllPrivileges.forEach(({ username, password }) => {
- it(`${username} should NOT be able to view alerts from "${featureIds.join(',')}" ${
- space != null ? `in space ${space}` : 'when no space specified'
- }`, async () => {
- // This will be flake until it uses the bsearch service, but these tests aren't operational. Once you do make this operational
- // use const bsearch = getService('bsearch');
- const resp = await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(space)}${TEST_URL}`)
- .auth(username, password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({ ...body })
- .expect(200);
-
- const timeline = resp.body;
-
- expect(timeline.totalCount).to.be(0);
- });
- });
- }
-
- unauthorizedUsers.forEach(({ username, password }) => {
- it(`${username} should NOT be able to access "${featureIds.join(',')}" ${
- space != null ? `in space ${space}` : 'when no space specified'
- }`, async () => {
- // This will be flake until it uses the bsearch service, but these tests aren't operational. Once you do make this operational
- // use const bsearch = getService('bsearch');
- await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(space)}${TEST_URL}`)
- .auth(username, password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({ ...body })
- // TODO - This should be updated to be a 403 once this ticket is resolved
- // https://github.com/elastic/kibana/issues/106005
- .expect(500);
- });
- });
- }
-
- describe('alerts authentication', () => {
- const authorizedSecSpace1 = [secOnly, secOnlyRead];
- const authorizedObsSpace1 = [obsOnly, obsOnlyRead];
- const authorizedSecObsSpace1 = [obsSec, obsSecRead];
-
- const authorizedSecSpace2 = [secOnlySpace2, secOnlyReadSpace2];
- const authorizedObsSpace2 = [obsOnlySpace2, obsOnlyReadSpace2];
- const authorizedSecObsSpace2 = [obsSecAllSpace2, obsSecReadSpace2];
-
- const authorizedSecInAllSpaces = [secOnlySpacesAll];
- const authorizedObsInAllSpaces = [obsOnlySpacesAll];
- const authorizedSecObsInAllSpaces = [obsSecSpacesAll];
-
- const authorizedInAllSpaces = [superUser, globalRead];
- const unauthorized = [noKibanaPrivileges];
-
- describe('Querying for Security Solution alerts only', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['siem'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem'],
- },
- authorizedUsers: [
- ...authorizedSecSpace1,
- ...authorizedSecObsSpace1,
- ...authorizedSecInAllSpaces,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- usersWithoutAllPrivileges: [...authorizedObsSpace1, ...authorizedObsInAllSpaces],
- unauthorizedUsers: [
- ...authorizedSecSpace2,
- ...authorizedObsSpace2,
- ...authorizedSecObsSpace2,
- ...unauthorized,
- ],
- });
-
- addTests({
- space: SPACE_2,
- featureIds: ['siem'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- alertConsumers: ['siem'],
- },
- authorizedUsers: [
- ...authorizedSecSpace2,
- ...authorizedSecObsSpace2,
- ...authorizedSecInAllSpaces,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- usersWithoutAllPrivileges: [...authorizedObsSpace2, ...authorizedObsInAllSpaces],
- unauthorizedUsers: [
- ...authorizedSecSpace1,
- ...authorizedObsSpace1,
- ...authorizedSecObsSpace1,
- ...unauthorized,
- ],
- });
- });
-
- describe('Querying for APM alerts only', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['apm'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- alertConsumers: ['apm'],
- },
- authorizedUsers: [
- ...authorizedObsSpace1,
- ...authorizedSecObsSpace1,
- ...authorizedObsInAllSpaces,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- usersWithoutAllPrivileges: [...authorizedSecSpace1, ...authorizedSecInAllSpaces],
- unauthorizedUsers: [
- ...authorizedSecSpace2,
- ...authorizedObsSpace2,
- ...authorizedSecObsSpace2,
- ...unauthorized,
- ],
- });
- addTests({
- space: SPACE_2,
- featureIds: ['apm'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- alertConsumers: ['apm'],
- },
- authorizedUsers: [
- ...authorizedObsSpace2,
- ...authorizedSecObsSpace2,
- ...authorizedObsInAllSpaces,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- usersWithoutAllPrivileges: [...authorizedSecSpace2, ...authorizedSecInAllSpaces],
- unauthorizedUsers: [
- ...authorizedSecSpace1,
- ...authorizedObsSpace1,
- ...authorizedSecObsSpace1,
- ...unauthorized,
- ],
- });
- });
-
- describe('Querying for multiple solutions', () => {
- describe('authorized for both security solution and apm', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['siem', 'apm'],
- expectedNumberAlerts: 4,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem', 'apm'],
- },
- authorizedUsers: [
- ...authorizedSecObsSpace1,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- unauthorizedUsers: [...unauthorized],
- });
- addTests({
- space: SPACE_2,
- featureIds: ['siem', 'apm'],
- expectedNumberAlerts: 4,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem', 'apm'],
- },
- authorizedUsers: [
- ...authorizedSecObsSpace2,
- ...authorizedSecObsInAllSpaces,
- ...authorizedInAllSpaces,
- ],
- unauthorizedUsers: [...unauthorized],
- });
- });
- describe('security solution privileges only', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['siem'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem', 'apm'],
- },
- authorizedUsers: [...authorizedSecInAllSpaces],
- unauthorizedUsers: [...unauthorized],
- });
- });
-
- describe('apm privileges only', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['apm'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem', 'apm'],
- },
- authorizedUsers: [...authorizedObsInAllSpaces],
- unauthorizedUsers: [...unauthorized],
- });
- });
-
- describe('querying from default space when no alerts were created in default space', () => {
- addTests({
- featureIds: ['siem'],
- expectedNumberAlerts: 0,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['siem', 'apm'],
- },
- authorizedUsers: [...authorizedSecInAllSpaces],
- unauthorizedUsers: [...unauthorized],
- });
- });
- });
- });
- });
-};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/index.ts
deleted file mode 100644
index 809e6d7cba75b..0000000000000
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/index.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-import {
- createSpacesAndUsers,
- deleteSpacesAndUsers,
-} from '../../../../../../../rule_registry/common/lib/authentication';
-
-export default ({ loadTestFile, getService }: FtrProviderContextWithSpaces): void => {
- describe('@ess timeline security and spaces enabled: basic', function () {
- before(async () => {
- await createSpacesAndUsers(getService);
- });
-
- after(async () => {
- await deleteSpacesAndUsers(getService);
- });
-
- // Basic
- loadTestFile(require.resolve('./events'));
- loadTestFile(require.resolve('./import_timelines'));
- loadTestFile(require.resolve('./install_prepackaged_timelines'));
- });
-};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/events.ts
deleted file mode 100644
index 6ff03cc9b2cc2..0000000000000
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/events.ts
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import Path from 'path';
-import Fs from 'fs';
-import { JsonObject } from '@kbn/utility-types';
-import expect from '@kbn/expect';
-import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
-
-import { TimelineEdges, TimelineNonEcsData } from '@kbn/timelines-plugin/common';
-import {
- Direction,
- TimelineEventsQueries,
-} from '@kbn/security-solution-plugin/common/search_strategy';
-import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
-import { User } from '../../../../../../../rule_registry/common/lib/authentication/types';
-import { getSpaceUrlPrefix } from '../../../../../../../rule_registry/common/lib/authentication/spaces';
-
-import {
- obsMinReadAlertsRead,
- obsMinReadAlertsReadSpacesAll,
- obsMinRead,
- obsMinReadSpacesAll,
- superUser,
-} from '../../../../../../../rule_registry/common/lib/authentication/users';
-import { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-
-class FileWrapper {
- constructor(private readonly path: string) {}
- async reset() {
- // "touch" each file to ensure it exists and is empty before each test
- await Fs.promises.writeFile(this.path, '');
- }
- async read() {
- const content = await Fs.promises.readFile(this.path, { encoding: 'utf8' });
- return content.trim().split('\n');
- }
- async readJSON() {
- const content = await this.read();
- return content.map((l) => JSON.parse(l));
- }
- // writing in a file is an async operation. we use this method to make sure logs have been written.
- async isNotEmpty() {
- const content = await this.read();
- const line = content[0];
- return line.length > 0;
- }
-}
-
-interface TestCase {
- /** The space where the alert exists */
- space?: string;
- /** The ID of the solution for which to get alerts */
- featureIds: string[];
- /** The total alerts expected to be returned */
- expectedNumberAlerts: number;
- /** body to be posted */
- body: JsonObject;
- /** Authorized users */
- authorizedUsers: User[];
- /** Unauthorized users */
- unauthorizedUsers: User[];
-}
-
-const TO = '3000-01-01T00:00:00.000Z';
-const FROM = '2000-01-01T00:00:00.000Z';
-const TEST_URL = '/internal/search/timelineSearchStrategy/';
-const SPACE_1 = 'space1';
-const SPACE_2 = 'space2';
-
-export default ({ getService }: FtrProviderContextWithSpaces) => {
- const esArchiver = getService('esArchiver');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
- const getPostBody = (): JsonObject => ({
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- factoryQueryType: TimelineEventsQueries.all,
- fieldRequested: ['@timestamp'],
- fields: [],
- filterQuery: {
- bool: {
- filter: [
- {
- match_all: {},
- },
- ],
- },
- },
- pagination: {
- activePage: 0,
- querySize: 25,
- },
- language: 'kuery',
- sort: [
- {
- field: '@timestamp',
- direction: Direction.desc,
- type: 'number',
- },
- ],
- timerange: {
- from: FROM,
- to: TO,
- interval: '12h',
- },
- });
-
- // TODO: Fix or update the tests
- describe.skip('Timeline - Events', () => {
- const logFilePath = Path.resolve(__dirname, '../../../common/audit.log');
- const logFile = new FileWrapper(logFilePath);
- const retry = getService('retry');
-
- before(async () => {
- await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
- });
- after(async () => {
- await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
- });
-
- function addTests({
- space,
- authorizedUsers,
- unauthorizedUsers,
- body,
- featureIds,
- expectedNumberAlerts,
- }: TestCase) {
- authorizedUsers.forEach(({ username, password }) => {
- it(`${username} should be able to view alerts from "${featureIds.join(',')}" ${
- space != null ? `in space ${space}` : 'when no space specified'
- }`, async () => {
- // This will be flake until it uses the bsearch service, but these tests aren't operational. Once you do make this operational
- // use const bsearch = getService('bsearch');
- const resp = await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(space)}${TEST_URL}`)
- .auth(username, password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({ ...body })
- .expect(200);
-
- const timeline = resp.body;
-
- expect(
- timeline.edges.every((hit: TimelineEdges) => {
- const data: TimelineNonEcsData[] = hit.node.data;
- return data.some(({ field, value }) => {
- return (
- field === ALERT_RULE_CONSUMER && featureIds.includes((value && value[0]) ?? '')
- );
- });
- })
- ).to.equal(true);
- expect(timeline.totalCount).to.be(expectedNumberAlerts);
- });
- });
-
- unauthorizedUsers.forEach(({ username, password }) => {
- it(`${username} should NOT be able to access "${featureIds.join(',')}" ${
- space != null ? `in space ${space}` : 'when no space specified'
- }`, async () => {
- // This will be flake until it uses the bsearch service, but these tests aren't operational. Once you do make this operational
- // use const bsearch = getService('bsearch');
- await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(space)}${TEST_URL}`)
- .auth(username, password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({ ...body })
- // TODO - This should be updated to be a 403 once this ticket is resolved
- // https://github.com/elastic/kibana/issues/106005
- .expect(500);
- });
- });
- }
-
- // TODO - tests need to be updated with new table logic
- describe('alerts authentication', () => {
- addTests({
- space: SPACE_1,
- featureIds: ['apm'],
- expectedNumberAlerts: 2,
- body: {
- ...getPostBody(),
- defaultIndex: ['.alerts*'],
- entityType: 'alerts',
- alertConsumers: ['apm'],
- },
- authorizedUsers: [obsMinReadAlertsRead, obsMinReadAlertsReadSpacesAll],
- unauthorizedUsers: [obsMinRead, obsMinReadSpacesAll],
- });
- });
-
- // FLAKY: https://github.com/elastic/kibana/issues/117462
- describe('logging', () => {
- beforeEach(async () => {
- await logFile.reset();
- });
-
- afterEach(async () => {
- await logFile.reset();
- });
-
- it('logs success events when reading alerts', async () => {
- await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(SPACE_1)}${TEST_URL}`)
- .auth(superUser.username, superUser.password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['apm'],
- })
- .expect(200);
- await retry.waitFor('logs event in the dest file', async () => await logFile.isNotEmpty());
-
- const content = await logFile.readJSON();
-
- const httpEvent = content.find((c) => c.event.action === 'http_request');
- expect(httpEvent).to.be.ok();
- expect(httpEvent.trace.id).to.be.ok();
- expect(httpEvent.user.name).to.be(superUser.username);
- expect(httpEvent.kibana.space_id).to.be('space1');
- expect(httpEvent.http.request.method).to.be('post');
- expect(httpEvent.url.path).to.be('/s/space1/internal/search/timelineSearchStrategy/');
-
- const findEvents = content.filter((c) => c.event.action === 'alert_find');
- expect(findEvents[0].trace.id).to.be.ok();
- expect(findEvents[0].event.outcome).to.be('success');
- expect(findEvents[0].user.name).to.be(superUser.username);
- expect(findEvents[0].kibana.space_id).to.be('space1');
- });
-
- it('logs failure events when unauthorized to read alerts', async () => {
- await supertestWithoutAuth
- .post(`${getSpaceUrlPrefix(SPACE_2)}${TEST_URL}`)
- .auth(obsMinRead.username, obsMinRead.password)
- .set(ELASTIC_HTTP_VERSION_HEADER, '1')
- .set('kbn-xsrf', 'true')
- .set('Content-Type', 'application/json')
- .send({
- ...getPostBody(),
- defaultIndex: ['.alerts-*'],
- entityType: 'alerts',
- alertConsumers: ['apm'],
- })
- .expect(500);
- await retry.waitFor('logs event in the dest file', async () => await logFile.isNotEmpty());
-
- const content = await logFile.readJSON();
-
- const httpEvent = content.find((c) => c.event.action === 'http_request');
- expect(httpEvent).to.be.ok();
- expect(httpEvent.trace.id).to.be.ok();
- expect(httpEvent.user.name).to.be(obsMinRead.username);
- expect(httpEvent.kibana.space_id).to.be(SPACE_2);
- expect(httpEvent.http.request.method).to.be('post');
- expect(httpEvent.url.path).to.be('/s/space2/internal/search/timelineSearchStrategy/');
-
- const findEvents = content.filter((c) => c.event.action === 'alert_find');
- expect(findEvents.length).to.equal(1);
- expect(findEvents[0].trace.id).to.be.ok();
- expect(findEvents[0].event.outcome).to.be('failure');
- expect(findEvents[0].user.name).to.be(obsMinRead.username);
- expect(findEvents[0].kibana.space_id).to.be(SPACE_2);
- });
- });
- });
-};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/index.ts
deleted file mode 100644
index 381d3a5b657bb..0000000000000
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/index.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-import {
- createSpaces,
- createUsersAndRoles,
- deleteSpaces,
- deleteUsersAndRoles,
-} from '../../../../../../../rule_registry/common/lib/authentication';
-
-import {
- observabilityMinReadAlertsRead,
- observabilityMinReadAlertsReadSpacesAll,
- observabilityMinimalRead,
- observabilityMinimalReadSpacesAll,
- observabilityMinReadAlertsAll,
- observabilityMinReadAlertsAllSpacesAll,
- observabilityMinimalAll,
- observabilityMinimalAllSpacesAll,
-} from '../../../../../../../rule_registry/common/lib/authentication/roles';
-import {
- obsMinReadAlertsRead,
- obsMinReadAlertsReadSpacesAll,
- obsMinRead,
- obsMinReadSpacesAll,
- superUser,
- obsMinReadAlertsAll,
- obsMinReadAlertsAllSpacesAll,
- obsMinAll,
- obsMinAllSpacesAll,
-} from '../../../../../../../rule_registry/common/lib/authentication/users';
-
-export default ({ loadTestFile, getService }: FtrProviderContextWithSpaces): void => {
- describe('@ess timeline security and spaces enabled: trial', function () {
- before(async () => {
- await createSpaces(getService);
- await createUsersAndRoles(
- getService,
- [
- obsMinReadAlertsRead,
- obsMinReadAlertsReadSpacesAll,
- obsMinRead,
- obsMinReadSpacesAll,
- superUser,
- obsMinReadAlertsAll,
- obsMinReadAlertsAllSpacesAll,
- obsMinAll,
- obsMinAllSpacesAll,
- ],
- [
- observabilityMinReadAlertsRead,
- observabilityMinReadAlertsReadSpacesAll,
- observabilityMinimalRead,
- observabilityMinimalReadSpacesAll,
- observabilityMinReadAlertsAll,
- observabilityMinReadAlertsAllSpacesAll,
- observabilityMinimalAll,
- observabilityMinimalAllSpacesAll,
- ]
- );
- });
-
- after(async () => {
- await deleteSpaces(getService);
- await deleteUsersAndRoles(
- getService,
- [
- obsMinReadAlertsRead,
- obsMinReadAlertsReadSpacesAll,
- obsMinRead,
- obsMinReadSpacesAll,
- superUser,
- obsMinReadAlertsAll,
- obsMinReadAlertsAllSpacesAll,
- obsMinAll,
- obsMinAllSpacesAll,
- ],
- [
- observabilityMinReadAlertsRead,
- observabilityMinReadAlertsReadSpacesAll,
- observabilityMinimalRead,
- observabilityMinimalReadSpacesAll,
- observabilityMinReadAlertsAll,
- observabilityMinReadAlertsAllSpacesAll,
- observabilityMinimalAll,
- observabilityMinimalAllSpacesAll,
- ]
- );
- });
-
- // Trial
- loadTestFile(require.resolve('./events'));
- });
-};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/events.ts
similarity index 94%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/events.ts
index c42ac64de4a23..9db1a5dfceb22 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/events.ts
@@ -15,9 +15,9 @@ import {
} from '@kbn/security-solution-plugin/common/search_strategy';
import TestAgent from 'supertest/lib/agent';
import { BsearchService } from '@kbn/ftr-common-functional-services';
-import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
-import { getFieldsToRequest, getFilterValue } from '../../../../utils';
+import { getFieldsToRequest, getFilterValue } from '../../../utils';
const TO = '3000-01-01T00:00:00.000Z';
const FROM = '2000-01-01T00:00:00.000Z';
@@ -60,7 +60,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) {
},
});
- describe('Timeline', () => {
+ describe('@skipInServerless Timeline', () => {
let supertest: TestAgent;
let bsearch: BsearchService;
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/import_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/import_timelines.ts
similarity index 98%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/import_timelines.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/import_timelines.ts
index 455780b333c8f..90f56e82a310a 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/import_timelines.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/import_timelines.ts
@@ -9,8 +9,8 @@ import expect from '@kbn/expect';
import { TIMELINE_IMPORT_URL } from '@kbn/security-solution-plugin/common/constants';
-import { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-import { deleteAllTimelines } from '../../../utils';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
+import { deleteAllTimelines } from '../utils';
export default ({ getService }: FtrProviderContextWithSpaces): void => {
const supertest = getService('supertest');
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
similarity index 65%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/index.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
index ebf592d01282b..0d14c693ea828 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts
@@ -5,14 +5,15 @@
* 2.0.
*/
-import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
export default function ({ loadTestFile }: FtrProviderContextWithSpaces) {
- // Failed in serverless: https://github.com/elastic/kibana/issues/183645
- describe('@ess @serverless @skipInServerless SecuritySolution Timeline', () => {
+ describe('@ess @serverless SecuritySolution Timeline', () => {
loadTestFile(require.resolve('./events'));
loadTestFile(require.resolve('./timeline_details'));
loadTestFile(require.resolve('./timeline'));
loadTestFile(require.resolve('./timeline_migrations'));
+ loadTestFile(require.resolve('./import_timelines'));
+ loadTestFile(require.resolve('./install_prepackaged_timelines'));
});
}
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/install_prepackaged_timelines.ts
similarity index 63%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/install_prepackaged_timelines.ts
index 3e85d2a85c399..58c9442b12159 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/install_prepackaged_timelines.ts
@@ -8,13 +8,12 @@
import expect from '@kbn/expect';
import { TIMELINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants';
-import { FtrProviderContextWithSpaces } from '../../../../../../ftr_provider_context_with_spaces';
-import { deleteAllTimelines, waitFor } from '../../../utils';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
+import { deleteAllTimelines } from '../utils';
export default ({ getService }: FtrProviderContextWithSpaces): void => {
const supertest = getService('supertest');
const es = getService('es');
- const log = getService('log');
describe('install_prepackaged_timelines', () => {
describe('creating prepackaged rules', () => {
@@ -22,15 +21,27 @@ export default ({ getService }: FtrProviderContextWithSpaces): void => {
await deleteAllTimelines(es);
});
- // TODO: Fix or update the tests
- it.skip('should contain timelines_installed, and timelines_updated', async () => {
+ it('should contain timelines_installed, and timelines_updated', async () => {
const { body } = await supertest
.post(TIMELINE_PREPACKAGED_URL)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
- expect(Object.keys(body)).to.eql(['timelines_installed', 'timelines_updated']);
+ expect(Object.keys(body)).to.eql([
+ 'success',
+ 'success_count',
+ 'timelines_installed',
+ 'timelines_updated',
+ 'errors',
+ ]);
+ expect(body).to.eql({
+ success: true,
+ success_count: 10,
+ errors: [],
+ timelines_installed: 10,
+ timelines_updated: 0,
+ });
});
it('should create the prepackaged timelines and return a count greater than zero', async () => {
@@ -53,29 +64,16 @@ export default ({ getService }: FtrProviderContextWithSpaces): void => {
expect(body.timelines_updated).to.eql(0);
});
- // TODO: Fix or update the tests
- it.skip('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => {
+ it('should be possible to call the API twice and the second time the number of timelines installed should be zero', async () => {
await supertest.post(TIMELINE_PREPACKAGED_URL).set('kbn-xsrf', 'true').send().expect(200);
- await waitFor(
- async () => {
- const { body } = await supertest
- .get(`${TIMELINE_PREPACKAGED_URL}/_status`)
- .set('kbn-xsrf', 'true')
- .expect(200);
- return body.timelines_not_installed === 0;
- },
- `${TIMELINE_PREPACKAGED_URL}/_status`,
- log
- );
-
- const { body } = await supertest
+ const { body: timelinePrepackagedResponseBody } = await supertest
.post(TIMELINE_PREPACKAGED_URL)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
- expect(body.timelines_installed).to.eql(0);
+ expect(timelinePrepackagedResponseBody.timelines_installed).to.eql(0);
});
});
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline.ts
similarity index 93%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline.ts
index 8a37ad6e9cac9..db5abd723f6f6 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline.ts
@@ -8,11 +8,11 @@
import expect from '@kbn/expect';
import { SavedTimeline, TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline';
-import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
import {
createBasicTimeline,
createBasicTimelineTemplate,
-} from '../../../saved_objects/trial_license_complete_tier/helpers';
+} from '../../saved_objects/tests/helpers';
export default function ({ getService }: FtrProviderContextWithSpaces) {
const supertest = getService('supertest');
@@ -60,7 +60,11 @@ export default function ({ getService }: FtrProviderContextWithSpaces) {
).to.equal(0);
});
});
- describe('resolve timeline', () => {
+ /**
+ * Migration of saved object not working to current serverless version
+ * https://github.com/elastic/kibana/issues/196483
+ * */
+ describe.skip('resolve timeline', () => {
before(async () => {
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/timelines/7.15.0'
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_details.ts
similarity index 94%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_details.ts
index 12539d43a145f..7d127ab7c0f96 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_details.ts
@@ -15,7 +15,7 @@ import {
import TestAgent from 'supertest/lib/agent';
import { BsearchService } from '@kbn/ftr-common-functional-services';
-import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
import { timelineDetailsFilebeatExpectedResults as EXPECTED_DATA } from '../mocks/timeline_details';
// typical values that have to change after an update from "scripts/es_archiver"
@@ -34,7 +34,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) {
const esArchiver = getService('esArchiver');
const utils = getService('securitySolutionUtils');
- describe('Timeline Details', () => {
+ describe('@skipInServerless Timeline Details', () => {
let supertest: TestAgent;
let bsearch: BsearchService;
before(async () => {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts
similarity index 97%
rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_migrations.ts
rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts
index c91d4ce24ce51..4aafce9938ae1 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_migrations.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts
@@ -16,8 +16,8 @@ import {
BarePinnedEventWithoutExternalRefs,
TimelineWithoutExternalRefs,
} from '@kbn/security-solution-plugin/common/api/timeline';
-import type { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces';
-import { getSavedObjectFromES } from '../../../../utils';
+import type { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
+import { getSavedObjectFromES } from '../../../utils';
interface TimelineWithoutSavedQueryId {
[timelineSavedObjectType]: TimelineWithoutExternalRefs;
@@ -34,7 +34,7 @@ interface PinnedEventWithoutTimelineId {
export default function ({ getService }: FtrProviderContextWithSpaces) {
const supertest = getService('supertest');
- describe('Timeline migrations', () => {
+ describe('@skipInServerless Timeline migrations', () => {
const esArchiver = getService('esArchiver');
const es = getService('es');
const kibanaServer = getService('kibanaServer');
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts
index 8fd44a2a2c5d9..8d9c8ad8a4652 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts
@@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`,
],
},
- testFiles: [require.resolve('../tests')],
+ testFiles: [require.resolve('../../tests')],
junit: {
reportName: 'Timeline Integration Tests - ESS Env - Trial License',
},
diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts
index 0a2827db15d66..0f8a8a350c3ed 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts
@@ -16,7 +16,7 @@ export default createTestConfig({
{ product_line: 'cloud', product_tier: 'complete' },
])}`,
],
- testFiles: [require.resolve('../tests')],
+ testFiles: [require.resolve('../../tests')],
junit: {
reportName: 'Timeline Integration Tests - Serverless Env - Complete Tier',
},
diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json
index 82decfa5a6db3..b7a320dd19720 100644
--- a/x-pack/test/security_solution_api_integration/tsconfig.json
+++ b/x-pack/test/security_solution_api_integration/tsconfig.json
@@ -45,7 +45,6 @@
"@kbn/actions-plugin",
"@kbn/task-manager-plugin",
"@kbn/utility-types",
- "@kbn/timelines-plugin",
"@kbn/dev-cli-runner",
"@kbn/elastic-assistant-common",
"@kbn/search-types",
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts
index d359a50c00c3e..8d44be4dc3aaf 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule.cy.ts
@@ -503,8 +503,6 @@ describe('indicator match', { tags: ['@ess', '@serverless', '@skipInServerlessMK
});
it('Investigate alert in timeline', () => {
- const accessibilityText = `Press enter for options, or press space to begin dragging.`;
-
loadPrepackagedTimelineTemplates();
createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })).then(
(rule) => visitRuleDetailsPage(rule.body.id)
@@ -525,14 +523,9 @@ describe('indicator match', { tags: ['@ess', '@serverless', '@skipInServerlessMK
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
'have.text',
- `threat.enrichments.matched.field${
- getNewThreatIndicatorRule().threat_mapping[0].entries[0].field
- }${accessibilityText}matched${
- getNewThreatIndicatorRule().threat_mapping[0].entries[0].field
- }${
+ `${getNewThreatIndicatorRule().threat_mapping[0].entries[0].field}matched${
indicatorRuleMatchingDoc.atomic
- }${accessibilityText}threat.enrichments.matched.typeindicator_match_rule${accessibilityText}provided` +
- ` byfeed.nameAbuseCH malware${accessibilityText}`
+ }indicator_match_ruleprovided` + ` byAbuseCH malware`
);
});
});
diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/sessions.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/sessions.ts
index c76ccb81f8ce2..c102f502f9489 100644
--- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/sessions.ts
+++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/sessions.ts
@@ -10,7 +10,6 @@ import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integrati
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
- const svlCommonApi = getService('svlCommonApi');
const samlAuth = getService('samlAuth');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestViewerWithCookieCredentials: SupertestWithRoleScopeType;
@@ -26,16 +25,6 @@ export default function ({ getService }: FtrProviderContext) {
});
describe('route access', () => {
- describe('disabled', () => {
- it('invalidate', async () => {
- const { body, status } = await supertestViewerWithCookieCredentials
- .post('/api/security/session/_invalidate')
- .set(samlAuth.getInternalRequestHeader())
- .send({ match: 'all' });
- svlCommonApi.assertApiNotFound(body, status);
- });
- });
-
describe('internal', () => {
it('get session info', async () => {
let body: any;
@@ -84,6 +73,45 @@ export default function ({ getService }: FtrProviderContext) {
// expect redirect
expect(status).toBe(302);
});
+
+ it('invalidate', async () => {
+ const supertestAdmin = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
+ useCookieHeader: true,
+ });
+
+ let body: any;
+ let status: number;
+
+ ({ body, status } = await supertestViewerWithCookieCredentials
+ .post('/api/security/session/_invalidate')
+ .set(samlAuth.getCommonRequestHeader()));
+ // expect a rejection because we're not using the internal header
+ expect(body).toEqual({
+ statusCode: 400,
+ error: 'Bad Request',
+ message: expect.stringContaining(
+ 'method [post] exists but is not available with the current configuration'
+ ),
+ });
+ expect(status).toBe(400);
+
+ ({ body, status } = await supertestViewerWithCookieCredentials
+ .post('/api/security/session/_invalidate')
+ .set(samlAuth.getInternalRequestHeader()));
+ // expect forbidden because the viewer does not have privilege to invalidate a session
+ expect(status).toBe(403);
+
+ ({ body, status } = await supertestAdmin
+ .post('/api/security/session/_invalidate')
+ .set(samlAuth.getInternalRequestHeader()));
+ // expect 400 due to no body, admin has privilege, but the request body is missing
+ expect(status).toBe(400);
+ expect(body).toEqual({
+ error: 'Bad Request',
+ message: '[request body]: expected a plain object value, but found [null] instead.',
+ statusCode: 400,
+ });
+ });
});
});
});
diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts
index 1bedd0acd0cc4..89edce106f64e 100644
--- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts
@@ -35,7 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
defaultIndex: 'logstash-*',
};
- describe('discover esql view', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/194305
+ describe.skip('discover esql view', function () {
// see details: https://github.com/elastic/kibana/issues/188816
this.tags(['failsOnMKI']);
diff --git a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
index d9d4389d4d63c..129f769283b34 100644
--- a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
@@ -30,13 +30,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('developer', function () {
before(async () => {
await pageObjects.svlCommonPage.loginWithRole('developer');
- await pageObjects.svlApiKeys.deleteAPIKeys();
});
after(async () => {
await deleteAllTestIndices();
});
beforeEach(async () => {
await deleteAllTestIndices();
+ await pageObjects.svlApiKeys.deleteAPIKeys();
await svlSearchNavigation.navigateToElasticsearchStartPage();
});
@@ -92,8 +92,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexUIView();
});
- // Failing: See https://github.com/elastic/kibana/issues/194673
- it.skip('should show the api key in code view', async () => {
+ it('should show the api key in code view', async () => {
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
@@ -131,8 +130,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
});
- // Failing: See https://github.com/elastic/kibana/issues/194673
- it.skip('Same API Key should be present on start page and index detail view', async () => {
+ it('Same API Key should be present on start page and index detail view', async () => {
await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
const apiKeyUI = await pageObjects.svlApiKeys.getAPIKeyFromUI();
diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
index aea757f7edea1..1cae648601d49 100644
--- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
@@ -53,6 +53,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchIndexDetailPage.expectConnectionDetails();
});
+ it('should show api key', async () => {
+ await pageObjects.svlApiKeys.deleteAPIKeys();
+ await svlSearchNavigation.navigateToIndexDetailPage(indexName);
+ await pageObjects.svlApiKeys.expectAPIKeyAvailable();
+ const apiKey = await pageObjects.svlApiKeys.getAPIKeyFromUI();
+ await pageObjects.svlSearchIndexDetailPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
+ });
+
it('should have quick stats', async () => {
await pageObjects.svlSearchIndexDetailPage.expectQuickStats();
await pageObjects.svlSearchIndexDetailPage.expectQuickStatsAIMappings();
@@ -89,13 +97,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
});
- // Failing: See https://github.com/elastic/kibana/issues/194673
- it.skip('should show api key', async () => {
- await pageObjects.svlApiKeys.expectAPIKeyAvailable();
- const apiKey = await pageObjects.svlApiKeys.getAPIKeyFromUI();
- await pageObjects.svlSearchIndexDetailPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
- });
-
describe('With data', () => {
before(async () => {
await es.index({
diff --git a/yarn.lock b/yarn.lock
index ed8af28c675f4..fa3904c57939e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11316,10 +11316,10 @@
dependencies:
"@types/react" "*"
-"@types/react-virtualized@^9.21.22":
- version "9.21.22"
- resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.22.tgz#5ba39b29869200620a6bf2069b8393f258a9c1e2"
- integrity sha512-YRifyCKnBG84+J/Hny0f3bo8BRrcNT74CvsAVpQpZcS83fdC7lP7RfzwL2ND8/ihhpnDFL1IbxJ9MpQNaKUDuQ==
+"@types/react-virtualized@^9.21.30":
+ version "9.21.30"
+ resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.30.tgz#ba39821bcb2487512a8a2cdd9fbdb5e6fc87fedb"
+ integrity sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ==
dependencies:
"@types/prop-types" "*"
"@types/react" "*"