diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md new file mode 100644 index 0000000000000..672d77719962f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md new file mode 100644 index 0000000000000..0d2c173f351c8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 76d0914173447..5acd837495dac 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-public.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md new file mode 100644 index 0000000000000..eabb966160c4d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) + +## ISessionService.delete property + +Deletes a session + +Signature: + +```typescript +delete: (sessionId: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md new file mode 100644 index 0000000000000..58e2fea0e6fe9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) + +## ISessionService.find property + +Gets a list of saved sessions + +Signature: + +```typescript +find: (options: SearchSessionFindOptions) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md new file mode 100644 index 0000000000000..a2dff2f18253b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) + +## ISessionService.get property + +Gets a saved session + +Signature: + +```typescript +get: (sessionId: string) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md new file mode 100644 index 0000000000000..8d8cd1f31bb95 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) + +## ISessionService.isRestore property + +Whether the active session is restored (i.e. reusing previous search IDs) + +Signature: + +```typescript +isRestore: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md new file mode 100644 index 0000000000000..db737880bb84e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) + +## ISessionService.isStored property + +Whether the active session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index 174f9dbe66bf4..02c0a821e552d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -15,8 +15,15 @@ export interface ISessionService | Property | Type | Description | | --- | --- | --- | | [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | +| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<void> | Deletes a session | +| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<SavedObjectsFindResponse<BackgroundSessionSavedObjectAttributes>> | Gets a list of saved sessions | +| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Gets a saved session | | [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | | [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | -| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => void | Restores existing session | +| [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) | () => boolean | Whether the active session is restored (i.e. reusing previous search IDs) | +| [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) | () => boolean | Whether the active session is already saved (i.e. sent to background) | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Restores existing session | +| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Saves a session | | [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | +| [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) | (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => Promise<any> | Updates a session | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md index 857e85bbd30eb..96106a6ef7e2d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -9,5 +9,5 @@ Restores existing session Signature: ```typescript -restore: (sessionId: string) => void; +restore: (sessionId: string) => Promise>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md new file mode 100644 index 0000000000000..4ac4a96614467 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) + +## ISessionService.save property + +Saves a session + +Signature: + +```typescript +save: (name: string, url: string) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md new file mode 100644 index 0000000000000..5e2ff53d58ab7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) + +## ISessionService.update property + +Updates a session + +Signature: + +```typescript +update: (sessionId: string, attributes: Partial) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md new file mode 100644 index 0000000000000..ae518e5a052fc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md new file mode 100644 index 0000000000000..aceee7fd6df68 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index af96e1413ba0c..85847e1c61d25 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-server.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 89a487ca8fb32..05d022d039b23 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -74,6 +74,21 @@ image::images/alert-flyout-add-action.png[You can add multiple actions on an ale Actions are not required on alerts. In some cases you may want to run an alert without actions first to understand its behavior, and configure actions later. ============================================== +[float] +=== Global actions configuration +Some actions configuration options apply to all actions. +If you are using an *on-prem* Elastic Stack deployment, you can set these in the kibana.yml file. +If you are using a cloud deployment, you can set these via the console. + +Here's a list of the available global configuration options and an explanation of what each one does: + +* `xpack.actions.allowedHosts`: Specifies an array of host names which actions such as email, Slack, PagerDuty, and webhook can connect to. An element of * indicates any host can be connected to. An empty array indicates no hosts can be connected to. Default: [ {asterisk} ] +* `xpack.actions.enabledActionTypes`: Specifies to an array of action types that are enabled. An {asterisk} indicates all action types registered are enabled. The action types that {kib} provides are: .server-log, .slack, .email, .index, .pagerduty, .webhook. Default: [ {asterisk} ] +* `xpack.actions.proxyUrl`: Specifies the proxy URL to use, if using a proxy for actions. +* `xpack.actions.proxyHeader`: Specifies HTTP headers for proxy, if using a proxy for actions. +* `xpack.actions.proxyRejectUnauthorizedCertificates`: Set to `false` to bypass certificate validation for proxy, if using a proxy for actions. +* `xpack.actions.rejectUnauthorized`: Set to `false` to bypass certificate validation for actions. + [float] === Managing alerts diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json index 9577ec353a4c9..07bb6a0b750e3 100644 --- a/examples/search_examples/kibana.json +++ b/examples/search_examples/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["navigation", "data", "developerExamples"], + "requiredPlugins": ["navigation", "data", "developerExamples", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/package.json b/package.json index 87e51abe49be3..23f7a0b430654 100644 --- a/package.json +++ b/package.json @@ -723,7 +723,7 @@ "less": "npm:@elastic/less@2.7.3-kibana", "license-checker": "^16.0.0", "listr": "^0.14.1", - "lmdb-store": "^0.8.11", + "lmdb-store": "^0.8.15", "load-grunt-config": "^3.0.1", "loader-utils": "^1.2.3", "log-symbols": "^2.2.0", diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts index 28790176af73d..46c46ad5d1b68 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts @@ -44,7 +44,7 @@ export function createIndexDocRecordsStream( ); }); - const resp = await client.bulk({ body }); + const resp = await client.bulk({ requestTimeout: 2 * 60 * 1000, body }); if (resp.errors) { throw new Error(`Failed to index all documents: ${JSON.stringify(resp, null, 2)}`); } diff --git a/packages/kbn-optimizer/src/node/cache.ts b/packages/kbn-optimizer/src/node/cache.ts index dc96bf47fafcf..a73dba5b16469 100644 --- a/packages/kbn-optimizer/src/node/cache.ts +++ b/packages/kbn-optimizer/src/node/cache.ts @@ -49,23 +49,19 @@ export class Cache { this.codes = LmdbStore.open({ name: 'codes', path: CACHE_DIR, - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 maxReaders: 500, }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.atimes = this.codes.openDB({ name: 'atimes', encoding: 'string', }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.mtimes = this.codes.openDB({ name: 'mtimes', encoding: 'string', }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.sourceMaps = this.codes.openDB({ name: 'sourceMaps', encoding: 'msgpack', diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index d744be9467311..8f53d6f7cf58b 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -60,7 +60,8 @@ export function runFtrCli() { include: toArray(flags['include-tag'] as string | string[]), exclude: toArray(flags['exclude-tag'] as string | string[]), }, - updateBaselines: flags.updateBaselines, + updateBaselines: flags.updateBaselines || flags.u, + updateSnapshots: flags.updateSnapshots || flags.u, } ); @@ -126,7 +127,16 @@ export function runFtrCli() { 'exclude-tag', 'kibana-install-dir', ], - boolean: ['bail', 'invert', 'test-stats', 'updateBaselines', 'throttle', 'headless'], + boolean: [ + 'bail', + 'invert', + 'test-stats', + 'updateBaselines', + 'updateSnapshots', + 'u', + 'throttle', + 'headless', + ], default: { config: 'test/functional/config.js', }, @@ -141,6 +151,8 @@ export function runFtrCli() { --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags --test-stats print the number of tests (included and excluded) to STDERR --updateBaselines replace baseline screenshots with whatever is generated from the test + --updateSnapshots replace inline and file snapshots with whatever is generated from the test + -u replace both baseline screenshots and snapshots --kibana-install-dir directory where the Kibana install being tested resides --throttle enable network throttling in Chrome browser --headless run browser in headless mode diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index 12390a95a4961..35b4b85e4d22a 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -28,10 +28,17 @@ import EventEmitter from 'events'; export interface Suite { suites: Suite[]; tests: Test[]; + title: string; + file?: string; + parent?: Suite; } export interface Test { fullTitle(): string; + title: string; + file?: string; + parent?: Suite; + isPassed: () => boolean; } export interface Runner extends EventEmitter { diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 6ed114d62e244..6f1519c079bee 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -138,7 +138,7 @@ export const schema = Joi.object() .default(), updateBaselines: Joi.boolean().default(false), - + updateSnapshots: Joi.boolean().default(false), browser: Joi.object() .keys({ type: Joi.string().valid('chrome', 'firefox', 'msedge').default('chrome'), diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js index 5c23be6361866..0f5f3df6bd413 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js @@ -21,6 +21,7 @@ import { isAbsolute } from 'path'; import { loadTracer } from '../load_tracer'; import { decorateMochaUi } from './decorate_mocha_ui'; +import { decorateSnapshotUi } from '../snapshots/decorate_snapshot_ui'; /** * Load an array of test files into a mocha instance @@ -31,7 +32,17 @@ import { decorateMochaUi } from './decorate_mocha_ui'; * @param {String} path * @return {undefined} - mutates mocha, no return value */ -export const loadTestFiles = ({ mocha, log, lifecycle, providers, paths, updateBaselines }) => { +export const loadTestFiles = ({ + mocha, + log, + lifecycle, + providers, + paths, + updateBaselines, + updateSnapshots, +}) => { + decorateSnapshotUi(lifecycle, updateSnapshots); + const innerLoadTestFile = (path) => { if (typeof path !== 'string' || !isAbsolute(path)) { throw new TypeError('loadTestFile() only accepts absolute paths'); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index 39eb69a151918..66b93ec001ac9 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -53,6 +53,7 @@ export async function setupMocha(lifecycle, log, config, providers) { providers, paths: config.get('testFiles'), updateBaselines: config.get('updateBaselines'), + updateSnapshots: config.get('updateSnapshots'), }); // Each suite has a tag that is the path relative to the root of the repo diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts new file mode 100644 index 0000000000000..abfbd8acea783 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Test } from '../../fake_mocha_types'; +import { Lifecycle } from '../lifecycle'; +import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; +import path from 'path'; +import fs from 'fs'; + +describe('decorateSnapshotUi', () => { + describe('when running a test', () => { + let lifecycle: Lifecycle; + beforeEach(() => { + lifecycle = new Lifecycle(); + decorateSnapshotUi(lifecycle, false); + }); + + it('passes when the snapshot matches the actual value', async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatchInline(`"foo"`); + }).not.toThrow(); + }); + + it('throws when the snapshot does not match the actual value', async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatchInline(`"bar"`); + }).toThrow(); + }); + + it('writes a snapshot to an external file if it does not exist', async () => { + const test: Test = { + title: 'Test', + file: __filename, + isPassed: () => true, + } as any; + + // @ts-expect-error + test.parent = { + file: __filename, + tests: [test], + suites: [], + }; + + await lifecycle.beforeEachTest.trigger(test); + + const snapshotFile = path.resolve( + __dirname, + '__snapshots__', + 'decorate_snapshot_ui.test.snap' + ); + + expect(fs.existsSync(snapshotFile)).toBe(false); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + await lifecycle.afterTestSuite.trigger(test.parent); + + expect(fs.existsSync(snapshotFile)).toBe(true); + + fs.unlinkSync(snapshotFile); + + fs.rmdirSync(path.resolve(__dirname, '__snapshots__')); + }); + }); + + describe('when updating snapshots', () => { + let lifecycle: Lifecycle; + beforeEach(() => { + lifecycle = new Lifecycle(); + decorateSnapshotUi(lifecycle, true); + }); + + it("doesn't throw if the value does not match", async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('bar').toMatchInline(`"foo"`); + }).not.toThrow(); + }); + }); +}); diff --git a/x-pack/test/apm_api_integration/common/match_snapshot.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts similarity index 66% rename from x-pack/test/apm_api_integration/common/match_snapshot.ts rename to packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 567a1ced360f8..45550b55e73c7 100644 --- a/x-pack/test/apm_api_integration/common/match_snapshot.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { @@ -14,8 +27,9 @@ import path from 'path'; import expect from '@kbn/expect'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; -import { Suite, Test } from 'mocha'; -import { flatten } from 'lodash'; +import { flatten, once } from 'lodash'; +import { Lifecycle } from '../lifecycle'; +import { Test, Suite } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -59,12 +73,38 @@ function getSnapshotMeta(currentTest: Test) { }; } -export function registerMochaHooksForSnapshots() { +const modifyStackTracePrepareOnce = once(() => { + const originalPrepareStackTrace = Error.prepareStackTrace; + + // jest-snapshot uses a stack trace to determine which file/line/column + // an inline snapshot should be written to. We filter out match_snapshot + // from the stack trace to prevent it from wanting to write to this file. + + Error.prepareStackTrace = (error, structuredStackTrace) => { + let filteredStrackTrace: NodeJS.CallSite[] = structuredStackTrace; + if (registered) { + filteredStrackTrace = filteredStrackTrace.filter((callSite) => { + // check for both compiled and uncompiled files + return !callSite.getFileName()?.match(/decorate_snapshot_ui\.(js|ts)/); + }); + } + + if (originalPrepareStackTrace) { + return originalPrepareStackTrace(error, filteredStrackTrace); + } + }; +}); + +export function decorateSnapshotUi(lifecycle: Lifecycle, updateSnapshots: boolean) { let snapshotStatesByFilePath: Record< string, { snapshotState: ISnapshotState; testsInFile: Test[] } > = {}; + registered = true; + + modifyStackTracePrepareOnce(); + addSerializer({ serialize: (num: number) => { return String(parseFloat(num.toPrecision(15))); @@ -74,15 +114,14 @@ export function registerMochaHooksForSnapshots() { }, }); - registered = true; - - beforeEach(function () { - const currentTest = this.currentTest!; + // @ts-expect-error + global.expectSnapshot = expectSnapshot; + lifecycle.beforeEachTest.add((currentTest: Test) => { const { file, snapshotTitle } = getSnapshotMeta(currentTest); if (!snapshotStatesByFilePath[file]) { - snapshotStatesByFilePath[file] = getSnapshotState(file, currentTest); + snapshotStatesByFilePath[file] = getSnapshotState(file, currentTest, updateSnapshots); } testContext = { @@ -95,17 +134,14 @@ export function registerMochaHooksForSnapshots() { }; }); - afterEach(function () { - testContext = null; - }); - - after(function () { - // save snapshot after tests complete + lifecycle.afterTestSuite.add(function (testSuite) { + // save snapshot & check unused after top-level test suite completes + if (testSuite.parent?.parent) { + return; + } const unused: string[] = []; - const isUpdatingSnapshots = process.env.UPDATE_SNAPSHOTS; - Object.keys(snapshotStatesByFilePath).forEach((file) => { const { snapshotState, testsInFile } = snapshotStatesByFilePath[file]; @@ -118,7 +154,7 @@ export function registerMochaHooksForSnapshots() { } }); - if (!isUpdatingSnapshots) { + if (!updateSnapshots) { unused.push(...snapshotState.getUncheckedKeys()); } else { snapshotState.removeUncheckedKeys(); @@ -131,36 +167,19 @@ export function registerMochaHooksForSnapshots() { throw new Error( `${unused.length} obsolete snapshot(s) found:\n${unused.join( '\n\t' - )}.\n\nRun tests again with \`UPDATE_SNAPSHOTS=1\` to remove them.` + )}.\n\nRun tests again with \`--updateSnapshots\` to remove them.` ); } snapshotStatesByFilePath = {}; - - registered = false; }); } -const originalPrepareStackTrace = Error.prepareStackTrace; - -// jest-snapshot uses a stack trace to determine which file/line/column -// an inline snapshot should be written to. We filter out match_snapshot -// from the stack trace to prevent it from wanting to write to this file. - -Error.prepareStackTrace = (error, structuredStackTrace) => { - const filteredStrackTrace = structuredStackTrace.filter((callSite) => { - return !callSite.getFileName()?.endsWith('match_snapshot.ts'); - }); - if (originalPrepareStackTrace) { - return originalPrepareStackTrace(error, filteredStrackTrace); - } -}; - function recursivelyGetTestsFromSuite(suite: Suite): Test[] { return suite.tests.concat(flatten(suite.suites.map((s) => recursivelyGetTestsFromSuite(s)))); } -function getSnapshotState(file: string, test: Test) { +function getSnapshotState(file: string, test: Test, updateSnapshots: boolean) { const dirname = path.dirname(file); const filename = path.basename(file); @@ -177,7 +196,7 @@ function getSnapshotState(file: string, test: Test) { const snapshotState = new SnapshotState( path.join(dirname + `/__snapshots__/` + filename.replace(path.extname(filename), '.snap')), { - updateSnapshot: process.env.UPDATE_SNAPSHOTS ? 'all' : 'new', + updateSnapshot: updateSnapshots ? 'all' : 'new', getPrettier: () => prettier, getBabelTraverse: () => babelTraverse, } diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap index 434c374d5d23d..ad2f82de87b82 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap @@ -16,6 +16,8 @@ Options: --bail Stop the test run at the first failure. --grep Pattern to select which tests to run. --updateBaselines Replace baseline screenshots with whatever is generated from the test. + --updateSnapshots Replace inline and file snapshots with whatever is generated from the test. + --u Replace both baseline screenshots and snapshots --include Files that must included to be run, can be included multiple times. --exclude Files that must NOT be included to be run, can be included multiple times. --include-tag Tags that suites must include to be run, can be included multiple times. @@ -48,6 +50,27 @@ Object { } `; +exports[`process options for run tests CLI accepts boolean value for updateSnapshots 1`] = ` +Object { + "assertNoneExcluded": false, + "configs": Array [ + /foo, + ], + "createLogger": [Function], + "esFrom": "snapshot", + "extraKbnOpts": undefined, + "suiteFiles": Object { + "exclude": Array [], + "include": Array [], + }, + "suiteTags": Object { + "exclude": Array [], + "include": Array [], + }, + "updateSnapshots": true, +} +`; + exports[`process options for run tests CLI accepts debug option 1`] = ` Object { "assertNoneExcluded": false, diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap index 6ede71a6c3940..02d11b0033d57 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap @@ -16,6 +16,8 @@ Options: --bail Stop the test run at the first failure. --grep Pattern to select which tests to run. --updateBaselines Replace baseline screenshots with whatever is generated from the test. + --updateSnapshots Replace inline and file snapshots with whatever is generated from the test. + --u Replace both baseline screenshots and snapshots --include Files that must included to be run, can be included multiple times. --exclude Files that must NOT be included to be run, can be included multiple times. --include-tag Tags that suites must include to be run, can be included multiple times. diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js index 94d510915d8e5..5af0fe7d7b61c 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js @@ -46,6 +46,12 @@ const options = { updateBaselines: { desc: 'Replace baseline screenshots with whatever is generated from the test.', }, + updateSnapshots: { + desc: 'Replace inline and file snapshots with whatever is generated from the test.', + }, + u: { + desc: 'Replace both baseline screenshots and snapshots', + }, include: { arg: '', desc: 'Files that must included to be run, can be included multiple times.', diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js index 35e4cef5b3a66..34a2d19c22a3f 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js @@ -76,6 +76,11 @@ describe('process options for run tests CLI', () => { expect(options).toMatchSnapshot(); }); + it('accepts boolean value for updateSnapshots', () => { + const options = processOptions({ updateSnapshots: true }, ['foo']); + expect(options).toMatchSnapshot(); + }); + it('accepts source value for esFrom', () => { const options = processOptions({ esFrom: 'source' }, ['foo']); expect(options).toMatchSnapshot(); diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js index 9f9a8f59fde9a..438e3585fc068 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js @@ -125,6 +125,22 @@ describe('run tests CLI', () => { expect(exitMock).not.toHaveBeenCalledWith(); }); + it('accepts boolean value for updateSnapshots', async () => { + global.process.argv.push('--updateSnapshots'); + + await runTestsCli(['foo']); + + expect(exitMock).not.toHaveBeenCalledWith(); + }); + + it('accepts boolean value for -u', async () => { + global.process.argv.push('-u'); + + await runTestsCli(['foo']); + + expect(exitMock).not.toHaveBeenCalledWith(); + }); + it('accepts source value for esFrom', async () => { global.process.argv.push('--esFrom', 'source'); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap index b54bf5dc84dd1..ba085b0868216 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap @@ -7,6 +7,13 @@ exports[`start servers CLI options accepts boolean value for updateBaselines 1`] " `; +exports[`start servers CLI options accepts boolean value for updateSnapshots 1`] = ` +" +functional_tests_server: invalid option [updateSnapshots] + ...stack trace... +" +`; + exports[`start servers CLI options rejects bail 1`] = ` " functional_tests_server: invalid option [bail] @@ -40,4 +47,4 @@ exports[`start servers CLI options rejects invalid options even if valid options functional_tests_server: invalid option [grep] ...stack trace... " -`; \ No newline at end of file +`; diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js index 3ceecb2806628..d63a8df2491a8 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js @@ -126,6 +126,15 @@ describe('start servers CLI', () => { checkMockConsoleLogSnapshot(logMock); }); + it('accepts boolean value for updateSnapshots', async () => { + global.process.argv.push('--updateSnapshots'); + + await startServersCli('foo'); + + expect(exitMock).toHaveBeenCalledWith(1); + checkMockConsoleLogSnapshot(logMock); + }); + it('accepts source value for esFrom', async () => { global.process.argv.push('--esFrom', 'source'); diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.js b/packages/kbn-test/src/functional_tests/lib/run_ftr.js index 14883ac977c43..d9389c8cbc154 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.js +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.js @@ -22,7 +22,7 @@ import { CliError } from './run_cli'; async function createFtr({ configPath, - options: { installDir, log, bail, grep, updateBaselines, suiteFiles, suiteTags }, + options: { installDir, log, bail, grep, updateBaselines, suiteFiles, suiteTags, updateSnapshots }, }) { const config = await readConfigFile(log, configPath); @@ -37,6 +37,7 @@ async function createFtr({ installDir, }, updateBaselines, + updateSnapshots, suiteFiles: { include: [...suiteFiles.include, ...config.get('suiteFiles.include')], exclude: [...suiteFiles.exclude, ...config.get('suiteFiles.exclude')], diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 3219e6cf3d6ee..6d94389f82caa 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -5,6 +5,9 @@ "src/**/*", "index.d.ts" ], + "exclude": [ + "types/ftr_globals/**/*" + ], "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, diff --git a/test/mocha_decorations.d.ts b/packages/kbn-test/types/ftr_globals/mocha.d.ts similarity index 73% rename from test/mocha_decorations.d.ts rename to packages/kbn-test/types/ftr_globals/mocha.d.ts index 5ad289eb4f1a3..d143b742b6dd8 100644 --- a/test/mocha_decorations.d.ts +++ b/packages/kbn-test/types/ftr_globals/mocha.d.ts @@ -19,27 +19,11 @@ import { Suite } from 'mocha'; -type Tags = - | 'ciGroup1' - | 'ciGroup2' - | 'ciGroup3' - | 'ciGroup4' - | 'ciGroup5' - | 'ciGroup6' - | 'ciGroup7' - | 'ciGroup8' - | 'ciGroup9' - | 'ciGroup10' - | 'ciGroup11' - | 'ciGroup12'; - -// We need to use the namespace here to match the Mocha definition declare module 'mocha' { interface Suite { /** * Assign tags to the test suite to determine in which CI job it should be run. */ - tags(tags: T | T[]): void; - tags(tags: T | T[]): void; + tags(tags: string[] | string): void; } } diff --git a/packages/kbn-test/types/ftr_globals/snapshots.d.ts b/packages/kbn-test/types/ftr_globals/snapshots.d.ts new file mode 100644 index 0000000000000..ab247a72991eb --- /dev/null +++ b/packages/kbn-test/types/ftr_globals/snapshots.d.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +declare const expectSnapshot: ( + received: any +) => { + toMatch: () => void; + toMatchInline: (_actual?: any) => void; +}; diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts index d8f7b5091eb8f..0feb43f8f1d4b 100644 --- a/src/plugins/data/common/search/session/index.ts +++ b/src/plugins/data/common/search/session/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './status'; export * from './types'; diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/common/search/session/mocks.ts index 370faaa640c56..4604e15e4e93b 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/common/search/session/mocks.ts @@ -27,5 +27,12 @@ export function getSessionServiceMock(): jest.Mocked { restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), + isStored: jest.fn(), + isRestore: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), }; } diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts new file mode 100644 index 0000000000000..1f6b6eb3084bb --- /dev/null +++ b/src/plugins/data/common/search/session/status.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export enum BackgroundSessionStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + EXPIRED = 'expired', +} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 6660b8395547f..d1ab22057695a 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -18,6 +18,7 @@ */ import { Observable } from 'rxjs'; +import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; export interface ISessionService { /** @@ -30,6 +31,17 @@ export interface ISessionService { * @returns `Observable` */ getSession$: () => Observable; + + /** + * Whether the active session is already saved (i.e. sent to background) + */ + isStored: () => boolean; + + /** + * Whether the active session is restored (i.e. reusing previous search IDs) + */ + isRestore: () => boolean; + /** * Starts a new session */ @@ -38,10 +50,58 @@ export interface ISessionService { /** * Restores existing session */ - restore: (sessionId: string) => void; + restore: (sessionId: string) => Promise>; /** * Clears the active session. */ clear: () => void; + + /** + * Saves a session + */ + save: (name: string, url: string) => Promise>; + + /** + * Gets a saved session + */ + get: (sessionId: string) => Promise>; + + /** + * Gets a list of saved sessions + */ + find: ( + options: SearchSessionFindOptions + ) => Promise>; + + /** + * Updates a session + */ + update: ( + sessionId: string, + attributes: Partial + ) => Promise; + + /** + * Deletes a session + */ + delete: (sessionId: string) => Promise; +} + +export interface BackgroundSessionSavedObjectAttributes { + name: string; + created: string; + expires: string; + status: string; + initialState: Record; + restoreState: Record; + idMapping: Record; +} + +export interface SearchSessionFindOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + filter?: string; } diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 7451edf5e2fa3..695ee34d3b468 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -92,4 +92,15 @@ export interface ISearchOptions { * A session ID, grouping multiple search requests into a single session. */ sessionId?: string; + + /** + * Whether the session is already saved (i.e. sent to background) + */ + isStored?: boolean; + + /** + * Whether the session is restored (i.e. search requests should re-use the stored search IDs, + * rather than starting from scratch) + */ + isRestore?: boolean; } diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 8b8686c51b9c1..4b602cb963a8f 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -19,3 +19,4 @@ /** @internal */ export { shortenDottedString } from './shorten_dotted_string'; +export { tapFirst } from './tap_first'; diff --git a/src/plugins/data/common/utils/tap_first.test.ts b/src/plugins/data/common/utils/tap_first.test.ts new file mode 100644 index 0000000000000..033ae59f8c715 --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.test.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { of } from 'rxjs'; +import { tapFirst } from './tap_first'; + +describe('tapFirst', () => { + it('should tap the first and only the first', () => { + const fn = jest.fn(); + of(1, 2, 3).pipe(tapFirst(fn)).subscribe(); + expect(fn).toBeCalledTimes(1); + expect(fn).lastCalledWith(1); + }); +}); diff --git a/src/plugins/data/common/utils/tap_first.ts b/src/plugins/data/common/utils/tap_first.ts new file mode 100644 index 0000000000000..2c783a3ef87f0 --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { pipe } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +export function tapFirst(next: (x: T) => void) { + let isFirst = true; + return pipe( + tap((x: T) => { + if (isFirst) next(x); + isFirst = false; + }) + ); +} diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 165e11517311c..6c4609e5506c2 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -70,10 +70,12 @@ import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject } from 'src/core/server'; -import { SavedObject as SavedObject_2 } from 'src/core/public'; +import { SavedObject } from 'kibana/server'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; +import { SavedObject as SavedObject_3 } from 'src/core/public'; import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; +import { SavedObjectsFindResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -1389,7 +1391,7 @@ export class IndexPatternsService { // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts // // (undocumented) - getCache: () => Promise[] | null | undefined>; + getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts @@ -1401,7 +1403,7 @@ export class IndexPatternsService { }>>; getTitles: (refresh?: boolean) => Promise; refreshFields: (indexPattern: IndexPattern) => Promise; - savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; + savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec; setDefault: (id: string, force?: boolean) => Promise; updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise; } @@ -1446,6 +1448,8 @@ export type ISearchGeneric = | undefined // @public (undocumented) export interface ISessionService { clear: () => void; + delete: (sessionId: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts + find: (options: SearchSessionFindOptions) => Promise>; + get: (sessionId: string) => Promise>; getSession$: () => Observable; getSessionId: () => string | undefined; - restore: (sessionId: string) => void; + isRestore: () => boolean; + isStored: () => boolean; + // Warning: (ae-forgotten-export) The symbol "BackgroundSessionSavedObjectAttributes" needs to be exported by the entry point index.d.ts + restore: (sessionId: string) => Promise>; + save: (name: string, url: string) => Promise>; start: () => string; + update: (sessionId: string, attributes: Partial) => Promise; } // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2069,7 +2082,7 @@ export class SearchInterceptor { // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) - protected runSearch(request: IKibanaSearchRequest, signal: AbortSignal, strategy?: string): Promise; + protected runSearch(request: IKibanaSearchRequest, options?: ISearchOptions): Promise; search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable; // @internal (undocumented) protected setupAbortSignal({ abortSignal, timeout, }: { diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 78e65802bcf99..3fadb723b27cd 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -126,18 +126,25 @@ export class SearchInterceptor { */ protected runSearch( request: IKibanaSearchRequest, - signal: AbortSignal, - strategy?: string + options?: ISearchOptions ): Promise { const { id, ...searchRequest } = request; - const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); - const body = JSON.stringify(searchRequest); + const path = trimEnd( + `/internal/search/${options?.strategy ?? ES_SEARCH_STRATEGY}/${id ?? ''}`, + '/' + ); + const body = JSON.stringify({ + sessionId: options?.sessionId, + isStored: options?.isStored, + isRestore: options?.isRestore, + ...searchRequest, + }); return this.deps.http.fetch({ method: 'POST', path, body, - signal, + signal: options?.abortSignal, }); } @@ -235,7 +242,7 @@ export class SearchInterceptor { abortSignal: options?.abortSignal, }); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - return from(this.runSearch(request, combinedSignal, options?.strategy)).pipe( + return from(this.runSearch(request, { ...options, abortSignal: combinedSignal })).pipe( catchError((e: Error) => { return throwError(this.handleSearchError(e, request, timeoutSignal, options)); }), diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index a172738812937..0141cff258a9f 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -19,9 +19,13 @@ import uuid from 'uuid'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; +import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { ConfigSchema } from '../../config'; -import { ISessionService } from '../../common/search'; +import { + ISessionService, + BackgroundSessionSavedObjectAttributes, + SearchSessionFindOptions, +} from '../../common'; export class SessionService implements ISessionService { private session$ = new BehaviorSubject(undefined); @@ -30,6 +34,18 @@ export class SessionService implements ISessionService { } private appChangeSubscription$?: Subscription; private curApp?: string; + private http!: HttpStart; + + /** + * Has the session already been stored (i.e. "sent to background")? + */ + private _isStored: boolean = false; + + /** + * Is this session a restored session (have these requests already been made, and we're just + * looking to re-use the previous search IDs)? + */ + private _isRestore: boolean = false; constructor( initializerContext: PluginInitializerContext, @@ -39,6 +55,8 @@ export class SessionService implements ISessionService { Make sure that apps don't leave sessions open. */ getStartServices().then(([coreStart]) => { + this.http = coreStart.http; + this.appChangeSubscription$ = coreStart.application.currentAppId$.subscribe((appName) => { if (this.sessionId) { const message = `Application '${this.curApp}' had an open session while navigating`; @@ -69,16 +87,63 @@ export class SessionService implements ISessionService { return this.session$.asObservable(); } + public isStored() { + return this._isStored; + } + + public isRestore() { + return this._isRestore; + } + public start() { + this._isStored = false; + this._isRestore = false; this.session$.next(uuid.v4()); return this.sessionId!; } public restore(sessionId: string) { + this._isStored = true; + this._isRestore = true; this.session$.next(sessionId); + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public clear() { + this._isStored = false; + this._isRestore = false; this.session$.next(undefined); } + + public async save(name: string, url: string) { + const response = await this.http.post(`/internal/session`, { + body: JSON.stringify({ + name, + url, + sessionId: this.sessionId, + }), + }); + this._isStored = true; + return response; + } + + public get(sessionId: string) { + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); + } + + public find(options: SearchSessionFindOptions) { + return this.http.post(`/internal/session`, { + body: JSON.stringify(options), + }); + } + + public update(sessionId: string, attributes: Partial) { + return this.http.put(`/internal/session/${encodeURIComponent(sessionId)}`, { + body: JSON.stringify(attributes), + }); + } + + public delete(sessionId: string) { + return this.http.delete(`/internal/session/${encodeURIComponent(sessionId)}`); + } } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts new file mode 100644 index 0000000000000..74b03c4d867e4 --- /dev/null +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsType } from 'kibana/server'; + +export const BACKGROUND_SESSION_TYPE = 'background-session'; + +export const backgroundSessionMapping: SavedObjectsType = { + name: BACKGROUND_SESSION_TYPE, + namespaceType: 'single', + hidden: true, + mappings: { + properties: { + name: { + type: 'keyword', + }, + created: { + type: 'date', + }, + expires: { + type: 'date', + }, + status: { + type: 'keyword', + }, + initialState: { + type: 'object', + enabled: false, + }, + restoreState: { + type: 'object', + enabled: false, + }, + idMapping: { + type: 'object', + enabled: false, + }, + }, + }, +}; diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 077f9380823d0..7cd4d319e6417 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,3 +20,4 @@ export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; +export { BACKGROUND_SESSION_TYPE, backgroundSessionMapping } from './background_session'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 4914726c85ef8..290e94ee7cf99 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,6 +17,8 @@ * under the License. */ +import type { RequestHandlerContext } from 'src/core/server'; +import { coreMock } from '../../../../core/server/mocks'; import { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; @@ -40,3 +42,22 @@ export function createSearchStartMock(): jest.Mocked { searchSource: searchSourceMock.createStartContract(), }; } + +export function createSearchRequestHandlerContext(): jest.Mocked { + return { + core: coreMock.createRequestHandlerContext(), + search: { + search: jest.fn(), + cancel: jest.fn(), + session: { + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + trackId: jest.fn(), + getId: jest.fn(), + }, + }, + }; +} diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index a4161fe47b388..ed519164c8e43 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -35,11 +35,18 @@ export function registerSearchRoute(router: IRouter): void { query: schema.object({}, { unknowns: 'allow' }), - body: schema.object({}, { unknowns: 'allow' }), + body: schema.object( + { + sessionId: schema.maybe(schema.string()), + isStored: schema.maybe(schema.boolean()), + isRestore: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ), }, }, async (context, request, res) => { - const searchRequest = request.body; + const { sessionId, isStored, isRestore, ...searchRequest } = request.body; const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); @@ -50,6 +57,9 @@ export function registerSearchRoute(router: IRouter): void { { abortSignal, strategy, + sessionId, + isStored, + isRestore, } ) .pipe(first()) diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts new file mode 100644 index 0000000000000..f697f6d5a5c2b --- /dev/null +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { MockedKeys } from '@kbn/utility-types/jest'; +import type { CoreSetup, RequestHandlerContext } from 'kibana/server'; +import type { DataPluginStart } from '../../plugin'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { createSearchRequestHandlerContext } from '../mocks'; +import { registerSessionRoutes } from './session'; + +describe('registerSessionRoutes', () => { + let mockCoreSetup: MockedKeys>; + let mockContext: jest.Mocked; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockContext = createSearchRequestHandlerContext(); + registerSessionRoutes(mockCoreSetup.http.createRouter()); + }); + + it('save calls session.save with sessionId and attributes', async () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const body = { sessionId, name }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, saveHandler]] = mockRouter.post.mock.calls; + + saveHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, { name }); + }); + + it('get calls session.get with sessionId', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, getHandler]] = mockRouter.get.mock.calls; + + getHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.get).toHaveBeenCalledWith(id); + }); + + it('find calls session.find with options', async () => { + const page = 1; + const perPage = 5; + const sortField = 'my_field'; + const sortOrder = 'desc'; + const filter = 'foo: bar'; + const body = { page, perPage, sortField, sortOrder, filter }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [, [, findHandler]] = mockRouter.post.mock.calls; + + findHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); + }); + + it('update calls session.update with id and attributes', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const expires = new Date().toISOString(); + const params = { id }; + const body = { name, expires }; + + const mockRequest = httpServerMock.createKibanaRequest({ params, body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, updateHandler]] = mockRouter.put.mock.calls; + + updateHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.update).toHaveBeenCalledWith(id, body); + }); + + it('delete calls session.delete with id', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, deleteHandler]] = mockRouter.delete.mock.calls; + + deleteHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.delete).toHaveBeenCalledWith(id); + }); +}); diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts new file mode 100644 index 0000000000000..93f07ecfb92ff --- /dev/null +++ b/src/plugins/data/server/search/routes/session.ts @@ -0,0 +1,201 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; + +export function registerSessionRoutes(router: IRouter): void { + router.post( + { + path: '/internal/session', + validate: { + body: schema.object({ + sessionId: schema.string(), + name: schema.string(), + expires: schema.maybe(schema.string()), + initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })), + restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), + }, + }, + async (context, request, res) => { + const { sessionId, name, expires, initialState, restoreState } = request.body; + + try { + const response = await context.search!.session.save(sessionId, { + name, + expires, + initialState, + restoreState, + }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.get( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + const response = await context.search!.session.get(id); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.post( + { + path: '/internal/session/_find', + validate: { + body: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + sortField: schema.maybe(schema.string()), + sortOrder: schema.maybe(schema.string()), + filter: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { page, perPage, sortField, sortOrder, filter } = request.body; + try { + const response = await context.search!.session.find({ + page, + perPage, + sortField, + sortOrder, + filter, + }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.delete( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + await context.search!.session.delete(id); + + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.put( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + name: schema.maybe(schema.string()), + expires: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + const { name, expires } = request.body; + try { + const response = await context.search!.session.update(id, { name, expires }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); +} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index d8aa588719e3e..b44980164d097 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, from, Observable } from 'rxjs'; import { pick } from 'lodash'; import { CoreSetup, @@ -29,7 +29,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, switchMap } from 'rxjs/operators'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { ISearchSetup, @@ -49,7 +49,7 @@ import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; -import { searchTelemetry } from '../saved_objects'; +import { BACKGROUND_SESSION_TYPE, searchTelemetry } from '../saved_objects'; import { IEsSearchRequest, IEsSearchResponse, @@ -70,10 +70,14 @@ import { } from '../../common/search/aggs/buckets/shard_delay'; import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; +import { BackgroundSessionService, ISearchSessionClient } from './session'; +import { registerSessionRoutes } from './routes/session'; +import { backgroundSessionMapping } from '../saved_objects'; +import { tapFirst } from '../../common/utils'; declare module 'src/core/server' { interface RequestHandlerContext { - search?: ISearchClient; + search?: ISearchClient & { session: ISearchSessionClient }; } } @@ -102,6 +106,7 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; + private sessionService: BackgroundSessionService = new BackgroundSessionService(); constructor( private initializerContext: PluginInitializerContext, @@ -121,12 +126,17 @@ export class SearchService implements Plugin { }; registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); + registerSessionRoutes(router); core.http.registerRouteHandlerContext('search', async (context, request) => { const [coreStart] = await core.getStartServices(); - return this.asScopedProvider(coreStart)(request); + const search = this.asScopedProvider(coreStart)(request); + const session = this.sessionService.asScopedProvider(coreStart)(request); + return { ...search, session }; }); + core.savedObjects.registerType(backgroundSessionMapping); + this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -223,6 +233,7 @@ export class SearchService implements Plugin { public stop() { this.aggsService.stop(); + this.sessionService.stop(); } private registerSearchStrategy = < @@ -248,7 +259,24 @@ export class SearchService implements Plugin { options.strategy ); - return strategy.search(searchRequest, options, deps); + // If this is a restored background search session, look up the ID using the provided sessionId + const getSearchRequest = async () => + !options.isRestore || searchRequest.id + ? searchRequest + : { + ...searchRequest, + id: await this.sessionService.getId(searchRequest, options, deps), + }; + + return from(getSearchRequest()).pipe( + switchMap((request) => strategy.search(request, options, deps)), + tapFirst((response) => { + if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; + this.sessionService.trackId(searchRequest, response.id, options, { + savedObjectsClient: deps.savedObjectsClient, + }); + }) + ); }; private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { @@ -273,7 +301,9 @@ export class SearchService implements Plugin { private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { return (request: KibanaRequest): ISearchClient => { - const savedObjectsClient = savedObjects.getScopedClient(request); + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); const deps = { savedObjectsClient, esClient: elasticsearch.client.asScoped(request), diff --git a/src/plugins/data/server/search/session/index.ts b/src/plugins/data/server/search/session/index.ts new file mode 100644 index 0000000000000..11b5b16a02b56 --- /dev/null +++ b/src/plugins/data/server/search/session/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { BackgroundSessionService, ISearchSessionClient } from './session_service'; diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts new file mode 100644 index 0000000000000..1ceebae967d4c --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -0,0 +1,233 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { BackgroundSessionStatus } from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { BackgroundSessionService } from './session_service'; +import { createRequestHash } from './utils'; + +describe('BackgroundSessionService', () => { + let savedObjectsClient: jest.Mocked; + let service: BackgroundSessionService; + + const mockSavedObject: SavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + idMapping: {}, + }, + references: [], + }; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + service = new BackgroundSessionService(); + }); + + it('save throws if `name` is not provided', () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + + it('get calls saved objects client', async () => { + savedObjectsClient.get.mockResolvedValue(mockSavedObject); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const response = await service.get(sessionId, { savedObjectsClient }); + + expect(response).toBe(mockSavedObject); + expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + it('find calls saved objects client', async () => { + const mockFindSavedObject = { + ...mockSavedObject, + score: 1, + }; + const mockResponse = { + saved_objects: [mockFindSavedObject], + total: 1, + per_page: 1, + page: 0, + }; + savedObjectsClient.find.mockResolvedValue(mockResponse); + + const options = { page: 0, perPage: 5 }; + const response = await service.find(options, { savedObjectsClient }); + + expect(response).toBe(mockResponse); + expect(savedObjectsClient.find).toHaveBeenCalledWith({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }); + + it('update calls saved objects client', async () => { + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; + savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const attributes = { name: 'new_name' }; + const response = await service.update(sessionId, attributes, { savedObjectsClient }); + + expect(response).toBe(mockUpdateSavedObject); + expect(savedObjectsClient.update).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }); + + it('delete calls saved objects client', async () => { + savedObjectsClient.delete.mockResolvedValue({}); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const response = await service.delete(sessionId, { savedObjectsClient }); + + expect(response).toEqual({}); + expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + describe('trackId', () => { + it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const isStored = false; + const name = 'my saved background search session'; + const created = new Date().toISOString(); + const expires = new Date().toISOString(); + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + + await service.save(sessionId, { name, created, expires }, { savedObjectsClient }); + + expect(savedObjectsClient.create).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + { + name, + created, + expires, + initialState: {}, + restoreState: {}, + status: BackgroundSessionStatus.IN_PROGRESS, + idMapping: { [requestHash]: searchId }, + }, + { id: sessionId } + ); + }); + + it('updates saved object when `isStored` is `true`', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const isStored = true; + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { + idMapping: { [requestHash]: searchId }, + }); + }); + }); + + describe('getId', () => { + it('throws if `sessionId` is not provided', () => { + const searchRequest = { params: {} }; + + expect(() => + service.getId(searchRequest, {}, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); + }); + + it('throws if there is not a saved object', () => { + const searchRequest = { params: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => + service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot( + `[Error: Cannot get search ID from a session that is not stored]` + ); + }); + + it('throws if not restoring a saved session', () => { + const searchRequest = { params: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => + service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: false }, + { savedObjectsClient } + ) + ).rejects.toMatchInlineSnapshot( + `[Error: Get search ID is only supported when restoring a session]` + ); + }); + + it('returns the search ID from the saved object ID mapping', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + idMapping: { [requestHash]: searchId }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValue(mockSession); + + const id = await service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: true }, + { savedObjectsClient } + ); + + expect(id).toBe(searchId); + }); + }); +}); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts new file mode 100644 index 0000000000000..eca5f428b8555 --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.ts @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { + BackgroundSessionSavedObjectAttributes, + IKibanaSearchRequest, + ISearchOptions, + SearchSessionFindOptions, + BackgroundSessionStatus, +} from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { createRequestHash } from './utils'; + +const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; + +export interface BackgroundSessionDependencies { + savedObjectsClient: SavedObjectsClientContract; +} + +export type ISearchSessionClient = ReturnType< + ReturnType +>; + +export class BackgroundSessionService { + /** + * Map of sessionId to { [requestHash]: searchId } + * @private + */ + private sessionSearchMap = new Map>(); + + constructor() {} + + public setup = () => {}; + + public start = (core: CoreStart) => { + return { + asScoped: this.asScopedProvider(core), + }; + }; + + public stop = () => { + this.sessionSearchMap.clear(); + }; + + // TODO: Generate the `userId` from the realm type/realm name/username + public save = async ( + sessionId: string, + { + name, + created = new Date().toISOString(), + expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), + status = BackgroundSessionStatus.IN_PROGRESS, + initialState = {}, + restoreState = {}, + }: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + if (!name) throw new Error('Name is required'); + + // Get the mapping of request hash/search ID for this session + const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); + const idMapping = Object.fromEntries(searchMap.entries()); + const attributes = { name, created, expires, status, initialState, restoreState, idMapping }; + const session = await savedObjectsClient.create( + BACKGROUND_SESSION_TYPE, + attributes, + { id: sessionId } + ); + + // Clear out the entries for this session ID so they don't get saved next time + this.sessionSearchMap.delete(sessionId); + + return session; + }; + + // TODO: Throw an error if this session doesn't belong to this user + public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.get( + BACKGROUND_SESSION_TYPE, + sessionId + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public find = ( + options: SearchSessionFindOptions, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.find({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public update = ( + sessionId: string, + attributes: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.update( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); + }; + + /** + * Tracks the given search request/search ID in the saved session (if it exists). Otherwise, just + * store it in memory until a saved session exists. + * @internal + */ + public trackId = async ( + searchRequest: IKibanaSearchRequest, + searchId: string, + { sessionId, isStored }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId || !searchId) return; + const requestHash = createRequestHash(searchRequest.params); + + // If there is already a saved object for this session, update it to include this request/ID. + // Otherwise, just update the in-memory mapping for this session for when the session is saved. + if (isStored) { + const attributes = { idMapping: { [requestHash]: searchId } }; + await this.update(sessionId, attributes, deps); + } else { + const map = this.sessionSearchMap.get(sessionId) ?? new Map(); + map.set(requestHash, searchId); + this.sessionSearchMap.set(sessionId, map); + } + }; + + /** + * Look up an existing search ID that matches the given request in the given session so that the + * request can continue rather than restart. + * @internal + */ + public getId = async ( + searchRequest: IKibanaSearchRequest, + { sessionId, isStored, isRestore }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId) { + throw new Error('Session ID is required'); + } else if (!isStored) { + throw new Error('Cannot get search ID from a session that is not stored'); + } else if (!isRestore) { + throw new Error('Get search ID is only supported when restoring a session'); + } + + const session = await this.get(sessionId, deps); + const requestHash = createRequestHash(searchRequest.params); + if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { + throw new Error('No search ID in this session matching the given search request'); + } + + return session.attributes.idMapping[requestHash]; + }; + + public asScopedProvider = ({ savedObjects }: CoreStart) => { + return (request: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); + const deps = { savedObjectsClient }; + return { + save: (sessionId: string, attributes: Partial) => + this.save(sessionId, attributes, deps), + get: (sessionId: string) => this.get(sessionId, deps), + find: (options: SearchSessionFindOptions) => this.find(options, deps), + update: (sessionId: string, attributes: Partial) => + this.update(sessionId, attributes, deps), + delete: (sessionId: string) => this.delete(sessionId, deps), + trackId: (searchRequest: IKibanaSearchRequest, searchId: string, options: ISearchOptions) => + this.trackId(searchRequest, searchId, options, deps), + getId: (searchRequest: IKibanaSearchRequest, options: ISearchOptions) => + this.getId(searchRequest, options, deps), + }; + }; + }; +} diff --git a/src/plugins/data/server/search/session/utils.test.ts b/src/plugins/data/server/search/session/utils.test.ts new file mode 100644 index 0000000000000..d190f892a7f84 --- /dev/null +++ b/src/plugins/data/server/search/session/utils.test.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createRequestHash } from './utils'; + +describe('data/search/session utils', () => { + describe('createRequestHash', () => { + it('ignores `preference`', () => { + const request = { + foo: 'bar', + }; + + const withPreference = { + ...request, + preference: 1234, + }; + + expect(createRequestHash(request)).toEqual(createRequestHash(withPreference)); + }); + }); +}); diff --git a/src/plugins/data/server/search/session/utils.ts b/src/plugins/data/server/search/session/utils.ts new file mode 100644 index 0000000000000..c3332f80b6e3f --- /dev/null +++ b/src/plugins/data/server/search/session/utils.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createHash } from 'crypto'; + +/** + * Generate the hash for this request so that, in the future, this hash can be used to look up + * existing search IDs for this request. Ignores the `preference` parameter since it generally won't + * match from one request to another identical request. + */ +export function createRequestHash(keys: Record) { + const { preference, ...params } = keys; + return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); +} diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index ce66610edf880..8d1699c4ad5ed 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -753,6 +753,8 @@ export class IndexPatternsService implements Plugin_3 { @@ -302,7 +304,7 @@ export class AlertsClient { result.attributes.consumer, ReadOperations.Get ); - return this.getAlertFromRaw(result.id, result.attributes, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } public async getAlertState({ id }: { id: string }): Promise { @@ -391,11 +393,13 @@ export class AlertsClient { type: 'alert', }); - const authorizedData = data.map(({ id, attributes, references }) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const authorizedData = data.map(({ id, attributes, updated_at, references }) => { ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer); return this.getAlertFromRaw( id, fields ? (pick(attributes, fields) as RawAlert) : attributes, + updated_at, references ); }); @@ -581,7 +585,6 @@ export class AlertsClient { params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, - updatedAt: new Date().toISOString(), }); try { updatedObject = await this.unsecuredSavedObjectsClient.create( @@ -604,7 +607,12 @@ export class AlertsClient { throw e; } - return this.getPartialAlertFromRaw(id, updatedObject.attributes, updatedObject.references); + return this.getPartialAlertFromRaw( + id, + updatedObject.attributes, + updatedObject.updated_at, + updatedObject.references + ); } private apiKeyAsAlertAttributes( @@ -669,7 +677,6 @@ export class AlertsClient { await this.createAPIKey(this.generateAPIKeyName(attributes.alertTypeId, attributes.name)), username ), - updatedAt: new Date().toISOString(), updatedBy: username, }); try { @@ -744,7 +751,6 @@ export class AlertsClient { username ), updatedBy: username, - updatedAt: new Date().toISOString(), }); try { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); @@ -823,7 +829,6 @@ export class AlertsClient { apiKey: null, apiKeyOwner: null, updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }), { version } ); @@ -870,7 +875,6 @@ export class AlertsClient { muteAll: true, mutedInstanceIds: [], updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -909,7 +913,6 @@ export class AlertsClient { muteAll: false, mutedInstanceIds: [], updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -954,7 +957,6 @@ export class AlertsClient { this.updateMeta({ mutedInstanceIds, updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }), { version } ); @@ -997,7 +999,6 @@ export class AlertsClient { alertId, this.updateMeta({ updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }), { version } @@ -1049,17 +1050,19 @@ export class AlertsClient { private getAlertFromRaw( id: string, rawAlert: RawAlert, + updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined ): Alert { // In order to support the partial update API of Saved Objects we have to support // partial updates of an Alert, but when we receive an actual RawAlert, it is safe // to cast the result to an Alert - return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; + return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; } private getPartialAlertFromRaw( id: string, - { createdAt, updatedAt, meta, scheduledTaskId, ...rawAlert }: Partial, + { createdAt, meta, scheduledTaskId, ...rawAlert }: Partial, + updatedAt: SavedObject['updated_at'] = createdAt, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 6d259029ac480..ee407b1a6d50c 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -196,7 +196,6 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', muteAll: false, mutedInstanceIds: [], actions: [ @@ -331,7 +330,6 @@ describe('create()', () => { "foo", ], "throttle": null, - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -420,7 +418,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -558,7 +555,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -635,7 +631,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -976,7 +971,6 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0', @@ -1098,7 +1092,6 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', enabled: false, meta: { versionApiKeyLastmodified: 'v7.10.0', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index 8c9ab9494a50a..11ce0027f82d8 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -45,8 +45,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('disable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -138,7 +136,6 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { @@ -193,7 +190,6 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts index feec1d1b9334a..16e83c42d8930 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts @@ -13,7 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -46,8 +46,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('enable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -188,7 +186,6 @@ describe('enable()', () => { meta: { versionApiKeyLastmodified: kibanaVersion, }, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -295,7 +292,6 @@ describe('enable()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index 3d7473a746986..1b3a776bd23e0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -79,7 +79,6 @@ describe('find()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 3f0c783f424d1..5c0d80f159b31 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -59,7 +59,6 @@ describe('get()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9bd61c0fe66d2..269b2eb2ab7a7 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -76,7 +76,6 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { createdBy: null, updatedBy: null, createdAt: mockedDateString, - updatedAt: mockedDateString, apiKey: null, apiKeyOwner: null, throttle: null, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts index 14ebca2135587..868fa3d8c6aa2 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -43,8 +43,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('muteAll()', () => { test('mutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -76,7 +74,6 @@ describe('muteAll()', () => { { muteAll: true, mutedInstanceIds: [], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts index c2188f128cb4d..05ca741f480ca 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('muteInstance()', () => { test('mutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -70,7 +68,6 @@ describe('muteInstance()', () => { '1', { mutedInstanceIds: ['2'], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts index d92304ab873be..5ef1af9b6f0ee 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('unmuteAll()', () => { test('unmutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -77,7 +75,6 @@ describe('unmuteAll()', () => { { muteAll: false, mutedInstanceIds: [], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts index 3486df98f2f05..88692239ac2fe 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('unmuteInstance()', () => { test('unmutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -71,7 +69,6 @@ describe('unmuteInstance()', () => { { mutedInstanceIds: [], updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', }, { version: '123' } ); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index d0bb2607f7a47..ad58e36ade722 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -140,8 +140,8 @@ describe('update()', () => { ], scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }, + updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -300,7 +300,6 @@ describe('update()', () => { "foo", ], "throttle": null, - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -363,7 +362,6 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -486,7 +484,6 @@ describe('update()', () => { "foo", ], "throttle": "5m", - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -537,7 +534,6 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -652,7 +648,6 @@ describe('update()', () => { "foo", ], "throttle": "5m", - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts index ca5f44078f513..af178a1fac5f5 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('updateApiKey()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -115,7 +113,6 @@ describe('updateApiKey()', () => { apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', @@ -165,7 +162,6 @@ describe('updateApiKey()', () => { enabled: true, apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index dfe122f56bc48..da30273e93c6b 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -16,7 +16,6 @@ export const AlertAttributesExcludedFromAAD = [ 'muteAll', 'mutedInstanceIds', 'updatedBy', - 'updatedAt', 'executionStatus', ]; @@ -29,7 +28,6 @@ export type AlertAttributesExcludedFromAADType = | 'muteAll' | 'mutedInstanceIds' | 'updatedBy' - | 'updatedAt' | 'executionStatus'; export function setupSavedObjects( diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index f40a7d9075eed..a6c92080f18be 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -62,9 +62,6 @@ "createdAt": { "type": "date" }, - "updatedAt": { - "type": "date" - }, "apiKey": { "type": "binary" }, diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index a4cbc18e13b47..8c9d10769b18a 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -261,48 +261,8 @@ describe('7.10.0 migrates with failure', () => { }); }); -describe('7.11.0', () => { - beforeEach(() => { - jest.resetAllMocks(); - encryptedSavedObjectsSetup.createMigration.mockImplementation( - (shouldMigrateWhenPredicate, migration) => migration - ); - }); - - test('add updatedAt field to alert - set to SavedObject updated_at attribute', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; - const alert = getMockData({}, true); - expect(migration711(alert, { log })).toEqual({ - ...alert, - attributes: { - ...alert.attributes, - updatedAt: alert.updated_at, - }, - }); - }); - - test('add updatedAt field to alert - set to createdAt when SavedObject updated_at is not defined', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; - const alert = getMockData({}); - expect(migration711(alert, { log })).toEqual({ - ...alert, - attributes: { - ...alert.attributes, - updatedAt: alert.attributes.createdAt, - }, - }); - }); -}); - -function getUpdatedAt(): string { - const updatedAt = new Date(); - updatedAt.setHours(updatedAt.getHours() + 2); - return updatedAt.toISOString(); -} - function getMockData( - overwrites: Record = {}, - withSavedObjectUpdatedAt: boolean = false + overwrites: Record = {} ): SavedObjectUnsanitizedDoc> { return { attributes: { @@ -335,7 +295,6 @@ function getMockData( ], ...overwrites, }, - updated_at: withSavedObjectUpdatedAt ? getUpdatedAt() : undefined, id: uuid.v4(), type: 'alert', }; diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index d8ebced03c5a6..0b2c86b84f67b 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,15 +37,8 @@ export function getMigrations( ) ); - const migrationAlertUpdatedAtDate = encryptedSavedObjects.createMigration( - // migrate all documents in 7.11 in order to add the "updatedAt" field - (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setAlertUpdatedAtDate) - ); - return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtDate, '7.11.0'), }; } @@ -66,19 +59,6 @@ function executeMigrationWithErrorHandling( }; } -const setAlertUpdatedAtDate = ( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc => { - const updatedAt = doc.updated_at || doc.attributes.createdAt; - return { - ...doc, - attributes: { - ...doc.attributes, - updatedAt, - }, - }; -}; - const consumersToChange: Map = new Map( Object.entries({ alerting: 'alerts', diff --git a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts index 8041ec551bb0d..50815c797e399 100644 --- a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts @@ -95,7 +95,6 @@ const DefaultAttributes = { muteAll: true, mutedInstanceIds: ['muted-instance-id-1', 'muted-instance-id-2'], updatedBy: 'someone', - updatedAt: '2019-02-12T21:01:22.479Z', }; const InvalidAttributes = { ...DefaultAttributes, foo: 'bar' }; diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 4ccf251540a15..dde1628156658 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -148,7 +148,6 @@ export interface RawAlert extends SavedObjectAttributes { createdBy: string | null; updatedBy: string | null; createdAt: string; - updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index d36741ae6576d..e1bd71caddb4d 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -76,8 +76,12 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { this.pendingCount$.next(this.pendingCount$.getValue() + 1); return doPartialSearch( - () => this.runSearch(request, combinedSignal, strategy), - (requestId) => this.runSearch({ ...request, id: requestId }, combinedSignal, strategy), + () => this.runSearch(request, { ...options, strategy, abortSignal: combinedSignal }), + (requestId) => + this.runSearch( + { ...request, id: requestId }, + { ...options, strategy, abortSignal: combinedSignal } + ), (r) => !r.isRunning, (response) => response.id, id, diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 53bcac02cb01d..2070610ceb20e 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -57,6 +57,7 @@ export const enhancedEsSearchStrategyProvider = ( utils.toSnakeCase({ ...(await getDefaultSearchParams(uiSettingsClient)), batchedReduceSize: 64, + keepOnCompletion: !!options.sessionId, // Always return an ID, even if the request completes quickly ...asyncOptions, ...request.params, }) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx index e9ac51b6a901c..c8872fe43a184 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx @@ -10,7 +10,7 @@ import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__/'; import React from 'react'; import { EuiBasicTable, EuiPagination, EuiButtonEmpty } from '@elastic/eui'; -import { EuiLink } from '../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { EnginesTable } from './engines_table'; @@ -50,7 +50,7 @@ describe('EnginesTable', () => { }); it('contains engine links which send telemetry', () => { - const engineLinks = wrapper.find(EuiLink); + const engineLinks = wrapper.find(EuiLinkTo); engineLinks.forEach((link) => { expect(link.prop('to')).toEqual('/engines/test-engine'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index 9591bbda1f7c2..7d69cd2b4d4da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -11,7 +11,7 @@ import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/reac import { i18n } from '@kbn/i18n'; import { TelemetryLogic } from '../../../shared/telemetry'; -import { EuiLink } from '../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; @@ -59,9 +59,9 @@ export const EnginesTable: React.FC = ({ defaultMessage: 'Name', }), render: (name: string) => ( - + {name} - + ), width: '30%', truncateText: true, @@ -122,12 +122,12 @@ export const EnginesTable: React.FC = ({ ), dataType: 'string', render: (name: string) => ( - + - + ), align: 'right', width: '100px', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx index a257ccde9f474..8ba2da11604c2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx @@ -12,7 +12,7 @@ import { useValues } from 'kea'; import { shallow } from 'enzyme'; import { EuiCard } from '@elastic/eui'; -import { EuiButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { ProductCard } from './'; @@ -29,7 +29,7 @@ describe('ProductCard', () => { expect(card.find('h2').text()).toEqual('Elastic App Search'); expect(card.find('.productCard__image').prop('src')).toEqual('as.jpg'); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('to')).toEqual('/app/enterprise_search/app_search'); expect(button.prop('children')).toEqual('Launch App Search'); @@ -47,7 +47,7 @@ describe('ProductCard', () => { expect(card.find('h2').text()).toEqual('Elastic Workplace Search'); expect(card.find('.productCard__image').prop('src')).toEqual('ws.jpg'); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search'); expect(button.prop('children')).toEqual('Launch Workplace Search'); @@ -63,7 +63,7 @@ describe('ProductCard', () => { const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('children')).toEqual('Setup Workplace Search'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index de553acf11f7b..954743f2d0439 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -10,7 +10,7 @@ import { snakeCase } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiCard, EuiTextColor } from '@elastic/eui'; -import { EuiButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry'; import { KibanaLogic } from '../../../shared/kibana'; @@ -63,7 +63,7 @@ export const ProductCard: React.FC = ({ product, image }) => { paddingSize="l" description={{product.CARD_DESCRIPTION}} footer={ - = ({ product, image }) => { } > {config.host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT} - + } /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx index 2a0e2ffc34f3f..bb5d7ab570a08 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText } from '@elastic/eui'; -import { EuiPanel } from '../../../shared/react_router_helpers'; +import { EuiPanelTo } from '../../../shared/react_router_helpers'; import CtaImage from './assets/getting_started.png'; import './setup_guide_cta.scss'; export const SetupGuideCta: React.FC = () => ( - + @@ -34,5 +34,5 @@ export const SetupGuideCta: React.FC = () => ( - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index b92a5bbf1c64e..a04357816886c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -9,7 +9,7 @@ import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton } from '../react_router_helpers'; +import { EuiButtonTo } from '../react_router_helpers'; import { KibanaLogic } from '../../shared/kibana'; import './error_state_prompt.scss'; @@ -90,12 +90,12 @@ export const ErrorStatePrompt: React.FC = () => { } actions={ - + - + } /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx index 9eaa2ba4c4d6f..a7cc21fa63f42 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { EuiLink as EuiLinkExternal } from '@elastic/eui'; -import { EuiLink } from '../react_router_helpers'; +import { EuiLink } from '@elastic/eui'; +import { EuiLinkTo } from '../react_router_helpers'; import { ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN } from '../../../../common/constants'; import { SideNav, SideNavLink, SideNavItem } from './'; @@ -42,7 +42,7 @@ describe('SideNavLink', () => { const wrapper = shallow(Link); expect(wrapper.type()).toEqual('li'); - expect(wrapper.find(EuiLink)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo)).toHaveLength(1); expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1); }); @@ -52,7 +52,7 @@ describe('SideNavLink', () => { Link ); - const externalLink = wrapper.find(EuiLinkExternal); + const externalLink = wrapper.find(EuiLink); expect(externalLink).toHaveLength(1); expect(externalLink.prop('href')).toEqual('http://website.com'); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx index c75a48d5af41d..8da8f45757961 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx @@ -9,8 +9,8 @@ import { useLocation } from 'react-router-dom'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; -import { EuiIcon, EuiTitle, EuiText, EuiLink as EuiLinkExternal } from '@elastic/eui'; // TODO: Remove EuiLinkExternal after full Kibana transition -import { EuiLink } from '../react_router_helpers'; +import { EuiIcon, EuiTitle, EuiText, EuiLink } from '@elastic/eui'; // TODO: Remove EuiLink after full Kibana transition +import { EuiLinkTo } from '../react_router_helpers'; import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../common/constants'; import { stripTrailingSlash } from '../../../../common/strip_slashes'; @@ -96,19 +96,14 @@ export const SideNavLink: React.FC = ({ return (
  • {isExternal ? ( - + // eslint-disable-next-line @elastic/eui/href-or-on-click + {children} - + ) : ( - + {children} - + )} {subNav &&
      {subNav}
    }
  • diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx index 05374cb5f0274..d0140b8730229 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -13,7 +13,7 @@ import { EuiTitle, EuiFlexGroup, EuiFlexItem, - EuiButton as EuiButtonExternal, + EuiButton, } from '@elastic/eui'; import { @@ -22,7 +22,7 @@ import { LICENSED_SUPPORT_URL, } from '../../../../common/constants'; -import { EuiButton } from '../react_router_helpers'; +import { EuiButtonTo } from '../react_router_helpers'; import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; import { LicensingLogic } from '../licensing'; @@ -89,18 +89,18 @@ export const NotFound: React.FC = ({ product = {} }) => { actions={ - + {i18n.translate('xpack.enterpriseSearch.notFound.action1', { defaultMessage: 'Back to your dashboard', })} - + - + {i18n.translate('xpack.enterpriseSearch.notFound.action2', { defaultMessage: 'Contact support', })} - + } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx similarity index 78% rename from x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx index 3a4585b6d9a71..37784fbf4ffb5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx @@ -12,7 +12,7 @@ import { EuiLink, EuiButton, EuiPanel } from '@elastic/eui'; import { mockKibanaValues, mockHistory } from '../../__mocks__'; -import { EuiReactRouterLink, EuiReactRouterButton, EuiReactRouterPanel } from './eui_link'; +import { EuiLinkTo, EuiButtonTo, EuiPanelTo } from './eui_components'; describe('EUI & React Router Component Helpers', () => { beforeEach(() => { @@ -20,26 +20,26 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders an EuiLink', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiLink)).toHaveLength(1); }); it('renders an EuiButton', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiButton)).toHaveLength(1); }); it('renders an EuiPanel', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiPanel)).toHaveLength(1); expect(wrapper.find(EuiPanel).prop('paddingSize')).toEqual('l'); }); it('passes down all ...rest props', () => { - const wrapper = shallow(); + const wrapper = shallow(); const link = wrapper.find(EuiLink); expect(link.prop('external')).toEqual(true); @@ -47,7 +47,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders with the correct href and onClick props', () => { - const wrapper = mount(); + const wrapper = mount(); const link = wrapper.find(EuiLink); expect(link.prop('onClick')).toBeInstanceOf(Function); @@ -56,7 +56,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders with the correct non-basenamed href when shouldNotCreateHref is passed', () => { - const wrapper = mount(); + const wrapper = mount(); const link = wrapper.find(EuiLink); expect(link.prop('href')).toEqual('/foo/bar'); @@ -65,7 +65,7 @@ describe('EUI & React Router Component Helpers', () => { describe('onClick', () => { it('prevents default navigation and uses React Router history', () => { - const wrapper = mount(); + const wrapper = mount(); const simulatedEvent = { button: 0, @@ -79,7 +79,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('does not prevent default browser behavior on new tab/window clicks', () => { - const wrapper = mount(); + const wrapper = mount(); const simulatedEvent = { shiftKey: true, @@ -92,7 +92,7 @@ describe('EUI & React Router Component Helpers', () => { it('calls inherited onClick actions in addition to default navigation', () => { const customOnClick = jest.fn(); // Can be anything from telemetry to a state reset - const wrapper = mount(); + const wrapper = mount(); wrapper.find(EuiLink).simulate('click', { shiftKey: true }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx similarity index 73% rename from x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx rename to x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx index 61f6b31d3e2e9..56beed8780707 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx @@ -20,7 +20,7 @@ import { letBrowserHandleEvent, createHref } from './'; * https://github.com/elastic/eui/blob/master/wiki/react-router.md#react-router-51 */ -interface EuiReactRouterProps { +interface ReactRouterProps { to: string; onClick?(): void; // Used to navigate outside of the React Router plugin basename but still within Kibana, @@ -28,7 +28,7 @@ interface EuiReactRouterProps { shouldNotCreateHref?: boolean; } -export const EuiReactRouterHelper: React.FC = ({ +export const ReactRouterHelper: React.FC = ({ to, onClick, shouldNotCreateHref, @@ -59,38 +59,38 @@ export const EuiReactRouterHelper: React.FC = ({ * Component helpers */ -type EuiReactRouterLinkProps = EuiLinkAnchorProps & EuiReactRouterProps; -export const EuiReactRouterLink: React.FC = ({ +type ReactRouterEuiLinkProps = ReactRouterProps & EuiLinkAnchorProps; +export const EuiLinkTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); -type EuiReactRouterButtonProps = EuiButtonProps & EuiReactRouterProps; -export const EuiReactRouterButton: React.FC = ({ +type ReactRouterEuiButtonProps = ReactRouterProps & EuiButtonProps; +export const EuiButtonTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); -type EuiReactRouterPanelProps = EuiPanelProps & EuiReactRouterProps; -export const EuiReactRouterPanel: React.FC = ({ +type ReactRouterEuiPanelProps = ReactRouterProps & EuiPanelProps; +export const EuiPanelTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts index 326bfb32e41f4..05a9450e4a348 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts @@ -6,8 +6,4 @@ export { letBrowserHandleEvent } from './link_events'; export { createHref, CreateHrefOptions } from './create_href'; -export { - EuiReactRouterLink as EuiLink, - EuiReactRouterButton as EuiButton, - EuiReactRouterPanel as EuiPanel, -} from './eui_link'; +export { EuiLinkTo, EuiButtonTo, EuiPanelTo } from './eui_components'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/box.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/box.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/connection_illustration.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/connection_illustration.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/loading_small.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/loading_small.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_circle.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_circle.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg new file mode 100644 index 0000000000000..7aac36a6fe3c5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg new file mode 100644 index 0000000000000..cc07fbbc50877 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg new file mode 100644 index 0000000000000..01e5a7735de12 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg new file mode 100644 index 0000000000000..aa9c3e5b45146 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg new file mode 100644 index 0000000000000..98d418244c22f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg new file mode 100644 index 0000000000000..6541b3f9e753f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts new file mode 100644 index 0000000000000..e749fb9482758 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import confluence from './confluence.svg'; +import custom from './custom.svg'; +import dropbox from './dropbox.svg'; +import github from './github.svg'; +import gmail from './gmail.svg'; +import googleDrive from './google_drive.svg'; +import jira from './jira.svg'; +import jiraServer from './jira_server.svg'; +import oneDrive from './onedrive.svg'; +import salesforce from './salesforce.svg'; +import serviceNow from './servicenow.svg'; +import sharePoint from './sharepoint.svg'; +import slack from './slack.svg'; +import zendesk from './zendesk.svg'; + +export const imagesFull = { + confluence, + confluenceCloud: confluence, + confluenceServer: confluence, + custom, + dropbox, + github, + githubEnterpriseServer: github, + gmail, + googleDrive, + jira, + jiraServer, + jiraCloud: jira, + oneDrive, + salesforce, + salesforceSandbox: salesforce, + serviceNow, + sharePoint, + slack, + zendesk, +} as { [key: string]: string }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg new file mode 100644 index 0000000000000..c12e55798d889 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg new file mode 100644 index 0000000000000..4dfd0fd910079 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg new file mode 100644 index 0000000000000..c390dff1e418f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg new file mode 100644 index 0000000000000..ef6d552949424 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg new file mode 100644 index 0000000000000..6388ec44d21d7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg new file mode 100644 index 0000000000000..aebfd7a8e49c0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg new file mode 100644 index 0000000000000..8f6fc0c987eaa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg new file mode 100644 index 0000000000000..8afd143dd9a7c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index 857b6f3aaf997..dec9e25fe2440 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -6,17 +6,17 @@ import React from 'react'; -// Prefer importing entire lodash library, e.g. import { get } from "lodash" -// eslint-disable-next-line no-restricted-imports -import _camelCase from 'lodash/camelCase'; +import { camelCase } from 'lodash'; -import { images } from '../assets'; +import { images } from '../assets/source_icons'; +import { imagesFull } from '../assets/sources_full_bleed'; interface SourceIconProps { serviceType: string; name: string; className?: string; wrapped?: boolean; + fullBleed?: boolean; } export const SourceIcon: React.FC = ({ @@ -24,8 +24,15 @@ export const SourceIcon: React.FC = ({ serviceType, className, wrapped, + fullBleed = false, }) => { - const icon = {name}; + const icon = ( + {name} + ); return wrapped ? (
    {icon} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx index 896b8f8f5b4c7..818d06c55dd12 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx @@ -23,7 +23,7 @@ import { EuiToolTip, } from '@elastic/eui'; -import { EuiLink } from '../../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../../shared/react_router_helpers'; import { SOURCE_STATUSES as statuses } from '../../../constants'; import { ContentSourceDetails } from '../../../types'; import { ADD_SOURCE_PATH, SOURCE_DETAILS_PATH, getContentSourcePath } from '../../../routes'; @@ -77,9 +77,9 @@ export const SourceRow: React.FC = ({ const imageClass = classNames('source-row__icon', { 'source-row__icon--loading': isIndexing }); const fixLink = ( - + Fix - + ); const remoteTooltip = ( @@ -159,13 +159,13 @@ export const SourceRow: React.FC = ({ {showFix && {fixLink}} {showDetails && ( - Details - + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index 11c0430a8b429..c0f8bf57989ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -26,7 +26,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { Group } from '../../../types'; import { ORG_SOURCES_PATH } from '../../../routes'; @@ -96,9 +96,9 @@ export const GroupManagerModal: React.FC = ({ const handleSelectAll = () => selectAll(allSelected ? [] : allItems); const sourcesButton = ( - + {ADD_SOURCE_BUTTON_TEXT} - + ); const emptyState = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 5cebb96d00eb9..9d33f810edae6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { EuiTableRow, EuiTableRowCell, EuiIcon } from '@elastic/eui'; import { TruncatedContent } from '../../../../shared/truncate'; -import { EuiLink } from '../../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../../shared/react_router_helpers'; import { Group } from '../../../types'; @@ -64,9 +64,9 @@ export const GroupRow: React.FC = ({ - + - +
    {GROUP_UPDATED_TEXT} @@ -93,9 +93,9 @@ export const GroupRow: React.FC = ({ )} - + - +
    diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx index cc50c4d0af5c4..85175d156f886 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx @@ -27,7 +27,7 @@ import { TableFilters } from './components/table_filters'; import { DEFAULT_META } from '../../../shared/constants'; import { EuiFieldSearch, EuiLoadingSpinner } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; const getSearchResults = jest.fn(); const openNewGroupModal = jest.fn(); @@ -138,7 +138,7 @@ describe('GroupOverview', () => { const action = shallow(); expect(action.find('[data-test-subj="InviteUsersButton"]')).toHaveLength(1); - expect(action.find(EuiLinkButton)).toHaveLength(1); + expect(action.find(EuiButtonTo)).toHaveLength(1); }); it('does not render inviteUsersButton when federated auth', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index 4064391c09893..97647f149bc9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -10,7 +10,7 @@ import { useActions, useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { AppLogic } from '../../app_logic'; @@ -61,7 +61,7 @@ export const Groups: React.FC = () => { if (newGroup && hasMessages) { messages[0].description = ( - { {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action', { defaultMessage: 'Manage Group', })} - + ); } const clearFilters = hasFiltersSet && ; const inviteUsersButton = !isFederatedAuth ? ( - + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action', { defaultMessage: 'Invite users', })} - + ) : null; const headerAction = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index 7251461b848a4..ed5136a6f7a4e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -21,7 +21,7 @@ import { EuiButtonEmptyProps, EuiLinkProps, } from '@elastic/eui'; -import sharedSourcesIcon from '../../components/shared/assets/share_circle.svg'; +import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { TelemetryLogic } from '../../../shared/telemetry'; import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 1d221b8b1eead..ce03d0eeb3826 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler, CustomHttpResponseOptions } from 'src/core/server'; +import { RequestHandler, ResponseHeaders, KnownHeaders } from 'src/core/server'; import { GetInfoResponse, InstallPackageResponse, @@ -103,15 +103,21 @@ export const getFileHandler: RequestHandler = { + + const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control']; + const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => { + const value = registryResponse.headers.get(knownHeader); + if (value !== null) { + headers[knownHeader] = value; + } + return headers; + }, {} as ResponseHeaders); + + return response.custom({ body: registryResponse.body, statusCode: registryResponse.status, - }; - if (contentType !== null) { - customResponseObj.headers = { 'Content-Type': contentType }; - } - return response.custom(customResponseObj); + headers: proxiedHeaders, + }); } catch (error) { return defaultIngestErrorHandler({ error, response }); } diff --git a/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts b/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts index 77de515c9cc46..43f3b2037e381 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts @@ -21,6 +21,7 @@ export const InventoryMetaResponseRT = rt.type({ export const InventoryMetaRequestRT = rt.type({ sourceId: rt.string, nodeType: ItemTypeRT, + currentTime: rt.number, }); export type InventoryMetaRequest = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 92aa015113b2a..2e5ddab77d374 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -124,7 +124,7 @@ export const Layout = () => { <> - + diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx index e9ffc56d8c47f..7bcb1270c30a5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx @@ -54,11 +54,12 @@ const wrapToolbarItems = ( interface Props { nodeType: InventoryItemType; + currentTime: number; } -export const Toolbar = ({ nodeType }: Props) => { +export const Toolbar = ({ nodeType, currentTime }: Props) => { const { sourceId } = useSourceContext(); - const { accounts, regions } = useInventoryMeta(sourceId, nodeType); + const { accounts, regions } = useInventoryMeta(sourceId, nodeType, currentTime); const ToolbarItems = findToolbar(nodeType); return wrapToolbarItems(ToolbarItems, accounts, regions); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts index b038491690a13..01811eb21a110 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts @@ -15,7 +15,11 @@ import { } from '../../../../../common/http_api/inventory_meta_api'; import { InventoryItemType } from '../../../../../common/inventory_models/types'; -export function useInventoryMeta(sourceId: string, nodeType: InventoryItemType) { +export function useInventoryMeta( + sourceId: string, + nodeType: InventoryItemType, + currentTime: number +) { const decodeResponse = (response: any) => { return pipe( InventoryMetaResponseRT.decode(response), @@ -29,6 +33,7 @@ export function useInventoryMeta(sourceId: string, nodeType: InventoryItemType) JSON.stringify({ sourceId, nodeType, + currentTime, }), decodeResponse ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 0412b1b0defbf..4cfa8871b0dcc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -45,7 +45,7 @@ export function useSnapshot( interval: '1m', to: currentTime, from: currentTime - 1200 * 1000, - lookbackSize: 20, + lookbackSize: 5, }; const { error, loading, response, makeRequest } = useHTTPRequest( diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts index 8b5271cb960c1..c784aa0f7d20b 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts @@ -33,7 +33,7 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { }, async (requestContext, request, response) => { try { - const { sourceId, nodeType } = pipe( + const { sourceId, nodeType, currentTime } = pipe( InventoryMetaRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); @@ -42,11 +42,13 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { requestContext.core.savedObjects.client, sourceId ); + const awsMetadata = await getCloudMetadata( framework, requestContext, configuration, - nodeType + nodeType, + currentTime ); return response.ok({ diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts index b4288dae0c221..af9e9c5f57c5b 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts @@ -25,9 +25,18 @@ export const getCloudMetadata = async ( framework: KibanaFramework, req: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, - nodeType: InventoryItemType + nodeType: InventoryItemType, + currentTime: number ): Promise => { const model = findInventoryModel(nodeType); + // Only run this for AWS modules, eventually we might have more. + if (model.requiredModule !== 'aws') { + return { + accounts: [], + projects: [], + regions: [], + }; + } const metricQuery = { allowNoIndices: true, @@ -36,7 +45,18 @@ export const getCloudMetadata = async ( body: { query: { bool: { - must: [{ match: { 'event.module': model.requiredModule } }], + must: [ + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: currentTime - 86400000, // 24 hours ago + lte: currentTime, + format: 'epoch_millis', + }, + }, + }, + { match: { 'event.module': model.requiredModule } }, + ], }, }, size: 0, diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts index 8ab0f4a44c85d..b3d960e30404f 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts @@ -34,7 +34,8 @@ export const findIntervalForMetrics = async ( const modules = await Promise.all( fields.map( - async (field) => await getDatasetForField(client, field as string, options.indexPattern) + async (field) => + await getDatasetForField(client, field as string, options.indexPattern, options.timerange) ) ); diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts index 85bb5b106c87c..15e6f7ba86d01 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts @@ -17,7 +17,8 @@ interface EventDatasetHit { export const getDatasetForField = async ( client: ESSearchClient, field: string, - indexPattern: string + indexPattern: string, + timerange: { field: string; to: number; from: number } ) => { const params = { allowNoIndices: true, @@ -25,9 +26,25 @@ export const getDatasetForField = async ( terminateAfter: 1, index: indexPattern, body: { - query: { exists: { field } }, + query: { + bool: { + filter: [ + { exists: { field } }, + { + range: { + [timerange.field]: { + gte: timerange.from, + lte: timerange.to, + format: 'epoch_millis', + }, + }, + }, + ], + }, + }, size: 1, _source: ['event.dataset'], + sort: [{ [timerange.field]: { order: 'desc' } }], }, }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts index 827e0901c1c01..833b5349f4b56 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts @@ -75,7 +75,10 @@ const aggregationsToModules = async ( const fields = await Promise.all( uniqueFields.map( async (field) => - await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias) + await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias, { + ...options.timerange, + field: options.sourceConfiguration.fields.timestamp, + }) ) ); return fields.filter((f) => f) as string[]; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts index 9332d5aee1f52..7a2985188dccf 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts @@ -23,12 +23,11 @@ export const getNodes = async ( snapshotRequest ); const metricsApiResponse = await queryAllData(client, metricsApiRequest); - return copyMissingMetrics( - transformMetricsApiResponseToSnapshotResponse( - metricsApiRequest, - snapshotRequest, - source, - metricsApiResponse - ) + const snapshotResponse = transformMetricsApiResponseToSnapshotResponse( + metricsApiRequest, + snapshotRequest, + source, + metricsApiResponse ); + return copyMissingMetrics(snapshotResponse); }; diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts index 5aba9b06a6ccf..d52afebcaa254 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts @@ -174,18 +174,18 @@ export type SizeStylePropertyDescriptor = }; export type VectorStylePropertiesDescriptor = { - [VECTOR_STYLES.SYMBOLIZE_AS]?: SymbolizeAsStylePropertyDescriptor; - [VECTOR_STYLES.FILL_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LINE_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LINE_WIDTH]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.ICON]?: IconStylePropertyDescriptor; - [VECTOR_STYLES.ICON_SIZE]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.ICON_ORIENTATION]?: OrientationStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_TEXT]?: LabelStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_SIZE]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_BORDER_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_BORDER_SIZE]?: LabelBorderSizeStylePropertyDescriptor; + [VECTOR_STYLES.SYMBOLIZE_AS]: SymbolizeAsStylePropertyDescriptor; + [VECTOR_STYLES.FILL_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LINE_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LINE_WIDTH]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.ICON]: IconStylePropertyDescriptor; + [VECTOR_STYLES.ICON_SIZE]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.ICON_ORIENTATION]: OrientationStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_TEXT]: LabelStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_SIZE]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_BORDER_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_BORDER_SIZE]: LabelBorderSizeStylePropertyDescriptor; }; export type StyleDescriptor = { diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 2ab8a70f2e4df..85391ea82cbf2 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -36,6 +36,7 @@ import { LayerDescriptor, VectorLayerDescriptor, VectorSourceRequestMeta, + VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; import { IVectorSource } from '../../sources/vector_source'; import { LICENSED_FEATURES } from '../../../licensed_features'; @@ -79,13 +80,15 @@ function getClusterStyleDescriptor( clusterSource: ESGeoGridSource ): VectorStyleDescriptor { const defaultDynamicProperties = getDefaultDynamicProperties(); - const clusterStyleDescriptor: VectorStyleDescriptor = { + const clusterStyleDescriptor: Omit & { + properties: Partial; + } = { type: LAYER_STYLE_TYPE.VECTOR, properties: { [VECTOR_STYLES.LABEL_TEXT]: { type: STYLE_TYPE.DYNAMIC, options: { - ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options, + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -95,7 +98,7 @@ function getClusterStyleDescriptor( [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -157,7 +160,7 @@ function getClusterStyleDescriptor( } }); - return clusterStyleDescriptor; + return clusterStyleDescriptor as VectorStyleDescriptor; } export interface BlendedVectorLayerArguments { diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index cdfe60946f5f9..fa82b9dc3b542 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -71,7 +71,7 @@ function createChoroplethLayerDescriptor({ [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: { name: joinKey, origin: FIELD_ORIGIN.JOIN, diff --git a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts index 6f9bb686459b5..5fa2524b1b790 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts @@ -100,7 +100,7 @@ export function createRegionMapLayerDescriptor({ [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: { name: joinKey, origin: FIELD_ORIGIN.JOIN, @@ -108,7 +108,7 @@ export function createRegionMapLayerDescriptor({ color: colorPallette ? colorPallette.value : 'Yellow to Red', type: COLOR_MAP_TYPE.ORDINAL, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions) .fieldMetaOptions, isEnabled: false, }, diff --git a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts index 5b89373f2db48..05616f6916f62 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts @@ -113,16 +113,16 @@ export function createTileMapLayerDescriptor({ const colorPallette = NUMERICAL_COLOR_PALETTES.find((pallette) => { return pallette.value.toLowerCase() === colorSchema.toLowerCase(); }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: metricStyleField, color: colorPallette ? colorPallette.value : 'Yellow to Red', type: COLOR_MAP_TYPE.ORDINAL, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions) .fieldMetaOptions, isEnabled: false, }, @@ -139,11 +139,11 @@ export function createTileMapLayerDescriptor({ styleProperties[VECTOR_STYLES.ICON_SIZE] = { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), maxSize: 18, field: metricStyleField, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions) .fieldMetaOptions, isEnabled: false, }, diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index b982e6452e8cb..060ff4d46fa2a 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -76,11 +76,9 @@ export interface ILayer { getType(): string | undefined; isVisible(): boolean; cloneDescriptor(): Promise; - renderStyleEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null; + renderStyleEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null; getInFlightRequestTokens(): symbol[]; getPrevRequestToken(dataId: string): symbol | undefined; destroy: () => void; @@ -437,16 +435,14 @@ export class AbstractLayer implements ILayer { return null; } - renderStyleEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null { + renderStyleEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null { const style = this.getStyleForEditing(); if (!style) { return null; } - return style.renderEditor({ layer: this, onStyleDescriptorChange }); + return style.renderEditor(onStyleDescriptorChange); } getIndexPatternIds(): string[] { diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index dea551866f4a9..7e8a216685bbd 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -50,7 +50,7 @@ function createDynamicFillColorDescriptor( return { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field, color: layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE ? 'Green to Red' : 'Yellow to Red', @@ -226,12 +226,12 @@ export function createLayerDescriptor({ origin: FIELD_ORIGIN.SOURCE, }; - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: createDynamicFillColorDescriptor(layer, metricStyleField), [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: metricStyleField, }, }, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts index 909cd93b3df7a..b52ce02acb5f0 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts @@ -68,7 +68,7 @@ function createSourceLayerDescriptor(indexPatternId: string, indexPatternTitle: ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[1] }, @@ -121,7 +121,7 @@ function createDestinationLayerDescriptor(indexPatternId: string, indexPatternTi ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[2] }, @@ -168,7 +168,7 @@ function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: st ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.LINE_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[1] }, @@ -176,7 +176,7 @@ function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: st [VECTOR_STYLES.LINE_WIDTH]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx index 0f596c47fc9b6..1fd6a9c9ecc8e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -77,7 +77,7 @@ export const clustersLayerWizardConfig: LayerWizard = { [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -87,7 +87,7 @@ export const clustersLayerWizardConfig: LayerWizard = { [VECTOR_STYLES.LABEL_TEXT]: { type: STYLE_TYPE.DYNAMIC, options: { - ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options, + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index c75698805225f..599f3b2dfbb02 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -41,11 +41,7 @@ export class HeatmapStyle implements IStyle { return LAYER_STYLE_TYPE.HEATMAP; } - renderEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }) { + renderEditor(onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void) { const onHeatmapColorChange = ({ colorRampName }: { colorRampName: string }) => { const styleDescriptor = HeatmapStyle.createDescriptor(colorRampName); onStyleDescriptorChange(styleDescriptor); diff --git a/x-pack/plugins/maps/public/classes/styles/style.ts b/x-pack/plugins/maps/public/classes/styles/style.ts index abaa6184b0ca4..de14ab990fa23 100644 --- a/x-pack/plugins/maps/public/classes/styles/style.ts +++ b/x-pack/plugins/maps/public/classes/styles/style.ts @@ -6,15 +6,10 @@ import { ReactElement } from 'react'; import { StyleDescriptor } from '../../../common/descriptor_types'; -import { ILayer } from '../layers/layer'; export interface IStyle { getType(): string; - renderEditor({ - layer, - onStyleDescriptorChange, - }: { - layer: ILayer; - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null; + renderEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null; } diff --git a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts index cac3913d3149d..dad26d4172e0a 100644 --- a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts +++ b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts @@ -21,7 +21,7 @@ export class TileStyle implements IStyle { return LAYER_STYLE_TYPE.TILE; } - renderEditor(/* { layer, onStyleDescriptorChange } */) { + renderEditor(/* onStyleDescriptorChange */) { return null; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx index 4527f56c04d2e..d45c33bbc3f57 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx @@ -14,7 +14,11 @@ import { DynamicColorForm } from './dynamic_color_form'; import { StaticColorForm } from './static_color_form'; import { ColorDynamicOptions, ColorStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleColorEditor(props: Props) { +type ColorEditorProps = Omit, 'children'> & { + swatches: string[]; +}; + +export function VectorStyleColorEditor(props: ColorEditorProps) { const colorForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx index aaa21ea315f36..586d4fc0576ad 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicLabelForm } from './dynamic_label_form'; +// @ts-expect-error import { StaticLabelForm } from './static_label_form'; +import { LabelDynamicOptions, LabelStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleLabelEditor(props) { +type LabelEditorProps = Omit, 'children'>; + +export function VectorStyleLabelEditor(props: LabelEditorProps) { const labelForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx index e344f72bd429a..c492f24661e71 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicSizeForm } from './dynamic_size_form'; +// @ts-expect-error import { StaticSizeForm } from './static_size_form'; +import { SizeDynamicOptions, SizeStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleSizeEditor(props) { +type SizeEditorProps = Omit, 'children'>; + +export function VectorStyleSizeEditor(props: SizeEditorProps) { const sizeForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx index 43b088074a30e..f3363a9443cfd 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx @@ -25,12 +25,12 @@ export interface Props { customStaticOptionLabel?: string; defaultStaticStyleOptions: StaticOptions; defaultDynamicStyleOptions: DynamicOptions; - disabled: boolean; + disabled?: boolean; disabledBy?: VECTOR_STYLES; fields: StyleField[]; onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: DynamicOptions) => void; onStaticStyleChange: (propertyName: VECTOR_STYLES, options: StaticOptions) => void; - styleProperty: IStyleProperty; + styleProperty: IStyleProperty; } export class StylePropEditor extends Component< @@ -42,7 +42,7 @@ export class StylePropEditor extends Component< _onTypeToggle = () => { if (this.props.styleProperty.isDynamic()) { // preserve current dynmaic style - this._prevDynamicStyleOptions = this.props.styleProperty.getOptions(); + this._prevDynamicStyleOptions = this.props.styleProperty.getOptions() as DynamicOptions; // toggle to static style this.props.onStaticStyleChange( this.props.styleProperty.getStyleName(), @@ -50,7 +50,7 @@ export class StylePropEditor extends Component< ); } else { // preserve current static style - this._prevStaticStyleOptions = this.props.styleProperty.getOptions(); + this._prevStaticStyleOptions = this.props.styleProperty.getOptions() as StaticOptions; // toggle to dynamic style this.props.onDynamicStyleChange( this.props.styleProperty.getStyleName(), @@ -61,7 +61,7 @@ export class StylePropEditor extends Component< _onFieldMetaOptionsChange = (fieldMetaOptions: FieldMetaOptions) => { const options = { - ...this.props.styleProperty.getOptions(), + ...(this.props.styleProperty.getOptions() as DynamicOptions), fieldMetaOptions, }; this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), options); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx index 2a983a32f0d82..bd6cda0b57f8d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicIconForm } from './dynamic_icon_form'; +// @ts-expect-error import { StaticIconForm } from './static_icon_form'; +import { IconDynamicOptions, IconStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleIconEditor(props) { +type IconEditorProps = Omit, 'children'>; + +export function VectorStyleIconEditor(props: IconEditorProps) { const iconForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx similarity index 64% rename from x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx index d577912efb830..95e32f0e9969b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx @@ -7,34 +7,95 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; +// @ts-expect-error import { VectorStyleSymbolizeAsEditor } from './symbol/vector_style_symbolize_as_editor'; import { VectorStyleIconEditor } from './symbol/vector_style_icon_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; +// @ts-expect-error import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_border_size_editor'; +// @ts-expect-error import { OrientationEditor } from './orientation/orientation_editor'; import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_palettes'; -import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; import { LABEL_BORDER_SIZES, VECTOR_STYLES, STYLE_TYPE, VECTOR_SHAPE_TYPE, } from '../../../../../common/constants'; -import { createStyleFieldsHelper } from '../style_fields_helper'; - -export class VectorStyleEditor extends Component { - state = { - styleFields: [], - defaultDynamicProperties: getDefaultDynamicProperties(), - defaultStaticProperties: getDefaultStaticProperties(), - supportedFeatures: undefined, - selectedFeature: null, - }; +import { createStyleFieldsHelper, StyleField, StyleFieldsHelper } from '../style_fields_helper'; +import { + ColorDynamicOptions, + ColorStaticOptions, + DynamicStylePropertyOptions, + IconDynamicOptions, + IconStaticOptions, + LabelDynamicOptions, + LabelStaticOptions, + SizeDynamicOptions, + SizeStaticOptions, + StaticStylePropertyOptions, + StylePropertyOptions, + VectorStylePropertiesDescriptor, +} from '../../../../../common/descriptor_types'; +import { IStyleProperty } from '../properties/style_property'; +import { SymbolizeAsProperty } from '../properties/symbolize_as_property'; +import { LabelBorderSizeProperty } from '../properties/label_border_size_property'; +import { StaticTextProperty } from '../properties/static_text_property'; +import { StaticSizeProperty } from '../properties/static_size_property'; +import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; + +export interface StyleProperties { + [key: string]: IStyleProperty; +} + +interface Props { + layer: IVectorLayer; + isPointsOnly: boolean; + isLinesOnly: boolean; + onIsTimeAwareChange: (isTimeAware: boolean) => void; + handlePropertyChange: (propertyName: VECTOR_STYLES, stylePropertyDescriptor: unknown) => void; + hasBorder: boolean; + styleProperties: StyleProperties; + isTimeAware: boolean; + showIsTimeAware: boolean; +} + +interface State { + styleFields: StyleField[]; + defaultDynamicProperties: VectorStylePropertiesDescriptor; + defaultStaticProperties: VectorStylePropertiesDescriptor; + supportedFeatures: VECTOR_SHAPE_TYPE[]; + selectedFeature: VECTOR_SHAPE_TYPE; + styleFieldsHelper?: StyleFieldsHelper; +} + +export class VectorStyleEditor extends Component { + private _isMounted: boolean = false; + + constructor(props: Props) { + super(props); + + let selectedFeature = VECTOR_SHAPE_TYPE.POLYGON; + if (props.isPointsOnly) { + selectedFeature = VECTOR_SHAPE_TYPE.POINT; + } else if (props.isLinesOnly) { + selectedFeature = VECTOR_SHAPE_TYPE.LINE; + } + + this.state = { + styleFields: [], + defaultDynamicProperties: getDefaultDynamicProperties(), + defaultStaticProperties: getDefaultStaticProperties(), + supportedFeatures: [], + selectedFeature, + }; + } componentWillUnmount() { this._isMounted = false; @@ -68,36 +129,20 @@ export class VectorStyleEditor extends Component { async _loadSupportedFeatures() { const supportedFeatures = await this.props.layer.getSource().getSupportedShapeTypes(); - if (!this._isMounted) { - return; - } - - if (!_.isEqual(supportedFeatures, this.state.supportedFeatures)) { + if (this._isMounted && !_.isEqual(supportedFeatures, this.state.supportedFeatures)) { this.setState({ supportedFeatures }); } - - if (this.state.selectedFeature === null) { - let selectedFeature = VECTOR_SHAPE_TYPE.POLYGON; - if (this.props.isPointsOnly) { - selectedFeature = VECTOR_SHAPE_TYPE.POINT; - } else if (this.props.isLinesOnly) { - selectedFeature = VECTOR_SHAPE_TYPE.LINE; - } - this.setState({ - selectedFeature: selectedFeature, - }); - } } - _handleSelectedFeatureChange = (selectedFeature) => { - this.setState({ selectedFeature }); + _handleSelectedFeatureChange = (selectedFeature: string) => { + this.setState({ selectedFeature: selectedFeature as VECTOR_SHAPE_TYPE }); }; - _onIsTimeAwareChange = (event) => { + _onIsTimeAwareChange = (event: EuiSwitchEvent) => { this.props.onIsTimeAwareChange(event.target.checked); }; - _onStaticStyleChange = (propertyName, options) => { + _onStaticStyleChange = (propertyName: VECTOR_STYLES, options: StaticStylePropertyOptions) => { const styleDescriptor = { type: STYLE_TYPE.STATIC, options, @@ -105,7 +150,7 @@ export class VectorStyleEditor extends Component { this.props.handlePropertyChange(propertyName, styleDescriptor); }; - _onDynamicStyleChange = (propertyName, options) => { + _onDynamicStyleChange = (propertyName: VECTOR_STYLES, options: DynamicStylePropertyOptions) => { const styleDescriptor = { type: STYLE_TYPE.DYNAMIC, options, @@ -115,18 +160,21 @@ export class VectorStyleEditor extends Component { _hasMarkerOrIcon() { const iconSize = this.props.styleProperties[VECTOR_STYLES.ICON_SIZE]; - return iconSize.isDynamic() || iconSize.getOptions().size > 0; + return iconSize.isDynamic() || (iconSize as StaticSizeProperty).getOptions().size > 0; } _hasLabel() { const label = this.props.styleProperties[VECTOR_STYLES.LABEL_TEXT]; return label.isDynamic() ? label.isComplete() - : label.getOptions().value != null && label.getOptions().value.length; + : (label as StaticTextProperty).getOptions().value != null && + (label as StaticTextProperty).getOptions().value.length; } _hasLabelBorder() { - const labelBorderSize = this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_SIZE]; + const labelBorderSize = this.props.styleProperties[ + VECTOR_STYLES.LABEL_BORDER_SIZE + ] as LabelBorderSizeProperty; return labelBorderSize.getOptions().size !== LABEL_BORDER_SIZES.NONE; } @@ -138,13 +186,18 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_FILL_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.FILL_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.FILL_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.FILL_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.FILL_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR] + .options as ColorDynamicOptions } /> ); @@ -159,13 +212,18 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LINE_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LINE_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LINE_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR] + .options as ColorDynamicOptions } /> ); @@ -178,13 +236,18 @@ export class VectorStyleEditor extends Component { disabledBy={VECTOR_STYLES.ICON_SIZE} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_WIDTH]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LINE_WIDTH)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LINE_WIDTH] as IStyleProperty< + SizeDynamicOptions | SizeStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LINE_WIDTH)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LINE_WIDTH].options + this.state.defaultStaticProperties[VECTOR_STYLES.LINE_WIDTH].options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH] + .options as SizeDynamicOptions } /> ); @@ -198,13 +261,19 @@ export class VectorStyleEditor extends Component { + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_TEXT)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_TEXT].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_TEXT] + .options as LabelStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT] + .options as LabelDynamicOptions } /> @@ -215,13 +284,19 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR] + .options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_COLOR] + .options as ColorDynamicOptions } /> @@ -231,13 +306,19 @@ export class VectorStyleEditor extends Component { disabledBy={VECTOR_STYLES.LABEL_TEXT} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_SIZE]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_SIZE)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_SIZE] as IStyleProperty< + SizeDynamicOptions | SizeStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_SIZE)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_SIZE].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_SIZE] + .options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_SIZE].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_SIZE] + .options as SizeDynamicOptions } /> @@ -248,13 +329,19 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_BORDER_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_BORDER_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] + .options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] + .options as ColorDynamicOptions } /> @@ -274,7 +361,11 @@ export class VectorStyleEditor extends Component { const hasMarkerOrIcon = this._hasMarkerOrIcon(); let iconOrientationEditor; let iconEditor; - if (this.props.styleProperties[VECTOR_STYLES.SYMBOLIZE_AS].isSymbolizedAsIcon()) { + if ( + (this.props.styleProperties[ + VECTOR_STYLES.SYMBOLIZE_AS + ] as SymbolizeAsProperty).isSymbolizedAsIcon() + ) { iconOrientationEditor = ( + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.ICON)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options + this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options as IconStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.ICON].options + this.state.defaultDynamicProperties[VECTOR_STYLES.ICON].options as IconDynamicOptions } /> @@ -341,13 +436,18 @@ export class VectorStyleEditor extends Component { + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.ICON_SIZE)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.ICON_SIZE].options + this.state.defaultStaticProperties[VECTOR_STYLES.ICON_SIZE].options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options + this.state.defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE] + .options as SizeDynamicOptions } /> @@ -385,7 +485,7 @@ export class VectorStyleEditor extends Component { _renderProperties() { const { supportedFeatures, selectedFeature, styleFieldsHelper } = this.state; - if (!supportedFeatures || !styleFieldsHelper) { + if (supportedFeatures.length === 0 || !styleFieldsHelper) { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts index 8613f9e1e946f..fbe643a401484 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts @@ -34,7 +34,7 @@ export async function createStyleFieldsHelper(fields: IField[]): Promise = {}, + isTimeAware = true + ) { return { type: LAYER_STYLE_TYPE.VECTOR, - properties: { ...getDefaultProperties(), ...properties }, + properties: { ...getDefaultStaticProperties(), ...properties }, isTimeAware, }; } static createDefaultStyleProperties(mapColors: string[]) { - return getDefaultProperties(mapColors); + return getDefaultStaticProperties(mapColors); } constructor( @@ -192,7 +188,7 @@ export class VectorStyle implements IVectorStyle { this._styleMeta = new StyleMeta(this._descriptor.__styleMeta); this._symbolizeAsStyleProperty = new SymbolizeAsProperty( - this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS]!.options, + this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS].options, VECTOR_STYLES.SYMBOLIZE_AS ); this._lineColorStyleProperty = this._makeColorProperty( @@ -237,7 +233,7 @@ export class VectorStyle implements IVectorStyle { VECTOR_STYLES.LABEL_BORDER_COLOR ); this._labelBorderSizeStyleProperty = new LabelBorderSizeProperty( - this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE]!.options, + this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE].options, VECTOR_STYLES.LABEL_BORDER_SIZE, this._labelSizeStyleProperty ); @@ -270,16 +266,10 @@ export class VectorStyle implements IVectorStyle { : (this._lineWidthStyleProperty as StaticSizeProperty).getOptions().size !== 0; } - renderEditor({ - layer, - onStyleDescriptorChange, - }: { - layer: ILayer; - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }) { + renderEditor(onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void) { const rawProperties = this.getRawProperties(); - const handlePropertyChange = (propertyName: VECTOR_STYLES, settings: any) => { - rawProperties[propertyName] = settings; // override single property, but preserve the rest + const handlePropertyChange = (propertyName: VECTOR_STYLES, stylePropertyDescriptor: any) => { + rawProperties[propertyName] = stylePropertyDescriptor; // override single property, but preserve the rest const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, this.isTimeAware()); onStyleDescriptorChange(vectorStyleDescriptor); }; @@ -293,9 +283,8 @@ export class VectorStyle implements IVectorStyle { return dynamicStyleProp.isFieldMetaEnabled(); }); - const styleProperties: VectorStylePropertiesDescriptor = {}; + const styleProperties: StyleProperties = {}; this.getAllStyleProperties().forEach((styleProperty) => { - // @ts-expect-error styleProperties[styleProperty.getStyleName()] = styleProperty; }); @@ -303,7 +292,7 @@ export class VectorStyle implements IVectorStyle { { test('Should use first color in DEFAULT_*_COLORS when no colors are used on the map', () => { const styleProperties = getDefaultStaticProperties([]); - expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#54B399'); - expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#41937c'); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR].options.color).toBe('#54B399'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR].options.color).toBe('#41937c'); }); test('Should next color in DEFAULT_*_COLORS when colors are used on the map', () => { const styleProperties = getDefaultStaticProperties(['#54B399']); - expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#6092C0'); - expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#4379aa'); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR].options.color).toBe('#6092C0'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR].options.color).toBe('#4379aa'); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts index 50321510c2ba8..fc152b9e5a974 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts @@ -37,22 +37,6 @@ export const POLYGON_STYLES = [ VECTOR_STYLES.LINE_WIDTH, ]; -export function getDefaultProperties(mapColors: string[] = []): VectorStylePropertiesDescriptor { - return { - ...getDefaultStaticProperties(mapColors), - [VECTOR_STYLES.SYMBOLIZE_AS]: { - options: { - value: SYMBOLIZE_AS_TYPES.CIRCLE, - }, - }, - [VECTOR_STYLES.LABEL_BORDER_SIZE]: { - options: { - size: LABEL_BORDER_SIZES.SMALL, - }, - }, - }; -} - export function getDefaultStaticProperties( mapColors: string[] = [] ): VectorStylePropertiesDescriptor { @@ -129,6 +113,16 @@ export function getDefaultStaticProperties( color: isDarkMode ? '#000000' : '#FFFFFF', }, }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { + value: SYMBOLIZE_AS_TYPES.CIRCLE, + }, + }, + [VECTOR_STYLES.LABEL_BORDER_SIZE]: { + options: { + size: LABEL_BORDER_SIZES.SMALL, + }, + }, }; } @@ -244,5 +238,15 @@ export function getDefaultDynamicProperties(): VectorStylePropertiesDescriptor { }, }, }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { + value: SYMBOLIZE_AS_TYPES.CIRCLE, + }, + }, + [VECTOR_STYLES.LABEL_BORDER_SIZE]: { + options: { + size: LABEL_BORDER_SIZES.SMALL, + }, + }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js b/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js index 69cf51fb29c0d..e460d7728a319 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js @@ -11,9 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPanel, EuiSpacer } from '@elast import { FormattedMessage } from '@kbn/i18n/react'; export function StyleSettings({ layer, updateStyleDescriptor }) { - const settingsEditor = layer.renderStyleEditor({ - onStyleDescriptorChange: updateStyleDescriptor, - }); + const settingsEditor = layer.renderStyleEditor(updateStyleDescriptor); if (!settingsEditor) { return null; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index fb1f2920aaceb..d14e09d9384a2 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -114,7 +114,8 @@ const expectedEditedtags = editedRule.tags.join(''); const expectedEditedIndexPatterns = editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns; -describe('Custom detection rules creation', () => { +// SKIP: https://github.com/elastic/kibana/issues/83769 +describe.skip('Custom detection rules creation', () => { before(() => { esArchiverLoad('timeline'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index eb8448233c624..6f995045dfc6a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -17,7 +17,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson'; -describe('Export rules', () => { +// SKIP: https://github.com/elastic/kibana/issues/83769 +describe.skip('Export rules', () => { before(() => { esArchiverLoad('export_rule'); cy.server(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx index cce6c72ca4cc5..65e25b788b3d9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx @@ -31,13 +31,20 @@ describe('RuleActionsField', () => { }, }, }); + + const messageVariables = { + context: [], + state: [], + params: [], + }; + const Component = () => { const field = useFormFieldMock(); const { form } = useForm(); return (
    - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 4ff1b4e4f20f3..0211788509db3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -12,17 +12,21 @@ import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../common/constants'; -import { SelectField, useFormContext } from '../../../../shared_imports'; +import { FieldHook, useFormContext } from '../../../../shared_imports'; import { ActionForm, ActionType, loadActionTypes, + ActionVariables, } from '../../../../../../triggers_actions_ui/public'; import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; -type ThrottleSelectField = typeof SelectField; +interface Props { + field: FieldHook; + messageVariables: ActionVariables; +} const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = @@ -34,7 +38,7 @@ const FieldErrorsContainer = styled.div` } `; -export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => { +export const RuleActionsField: React.FC = ({ field, messageVariables }) => { const [fieldErrors, setFieldErrors] = useState(null); const [supportedActionTypes, setSupportedActionTypes] = useState(); const form = useFormContext(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx index a1d7e69b7a60f..565998806033c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx @@ -30,10 +30,20 @@ jest.mock('../../../../common/lib/kibana', () => ({ }), })); +const actionMessageParams = { + context: [], + state: [], + params: [], +}; + describe('StepRuleActions', () => { it('renders correctly', () => { const wrapper = shallow( - + ); expect(wrapper.find('[data-test-subj="stepRuleActions"]')).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx index dd1d92e7e72a3..daf4e3c01bbb3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx @@ -16,7 +16,7 @@ import { import { findIndex } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useMemo } from 'react'; -import { ActionVariable } from '../../../../../../triggers_actions_ui/public'; +import { ActionVariables } from '../../../../../../triggers_actions_ui/public'; import { RuleStep, RuleStepProps, @@ -38,7 +38,7 @@ import { APP_ID } from '../../../../../common/constants'; interface StepRuleActionsProps extends RuleStepProps { defaultValues?: ActionsStepRule | null; - actionMessageParams: ActionVariable[]; + actionMessageParams: ActionVariables; } const stepActionsDefaultValue: ActionsStepRule = { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index ffcf25d253798..513982f099c61 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -11,7 +11,7 @@ import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; import { EuiFlexItem } from '@elastic/eui'; -import { ActionVariable } from '../../../../../../triggers_actions_ui/public'; +import { ActionVariables } from '../../../../../../triggers_actions_ui/public'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { assertUnreachable } from '../../../../../common/utility_types'; import { transformRuleToAlertAction } from '../../../../../common/detection_engine/transform_actions'; @@ -366,21 +366,26 @@ export const getActionMessageRuleParams = (ruleType: Type): string[] => { return ruleParamsKeys; }; -export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): ActionVariable[] => { - if (!ruleType) { - return []; +export const getActionMessageParams = memoizeOne( + (ruleType: Type | undefined): ActionVariables => { + if (!ruleType) { + return { state: [], params: [] }; + } + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + // Prefixes are being added automatically by the ActionTypeForm + return { + state: [{ name: 'signals_count', description: 'state.signals_count' }], + params: [], + context: [ + { name: 'results_link', description: 'context.results_link' }, + ...actionMessageRuleParams.map((param) => { + const extendedParam = `rule.${param}`; + return { name: extendedParam, description: `context.${extendedParam}` }; + }), + ], + }; } - const actionMessageRuleParams = getActionMessageRuleParams(ruleType); - - return [ - { name: 'state.signals_count', description: 'state.signals_count' }, - { name: '{context.results_link}', description: 'context.results_link' }, - ...actionMessageRuleParams.map((param) => { - const extendedParam = `context.rule.${param}`; - return { name: extendedParam, description: extendedParam }; - }), - ]; -}); +); // typed as null not undefined as the initial state for this value is null. export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 71e1c60a92aed..226b9de8b677f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -16,6 +16,8 @@ import { chartPluginMock } from '../../../../../../../../src/plugins/charts/publ import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; import { featuresPluginMock } from '../../../../../../features/public/mocks'; +import { ActionConnector } from '../../../../types'; +import { times } from 'lodash'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -109,36 +111,38 @@ describe('actions_connectors_list component empty', () => { describe('actions_connectors_list component with items', () => { let wrapper: ReactWrapper; - async function setup() { + async function setup(actionConnectors?: ActionConnector[]) { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce([ - { - id: '1', - actionTypeId: 'test', - description: 'My test', - isPreconfigured: false, - referencedByCount: 1, - config: {}, - }, - { - id: '2', - actionTypeId: 'test2', - description: 'My test 2', - referencedByCount: 1, - isPreconfigured: false, - config: {}, - }, - { - id: '3', - actionTypeId: 'test2', - description: 'My preconfigured test 2', - referencedByCount: 1, - isPreconfigured: true, - config: {}, - }, - ]); + loadAllActions.mockResolvedValueOnce( + actionConnectors ?? [ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + isPreconfigured: false, + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + isPreconfigured: false, + config: {}, + }, + { + id: '3', + actionTypeId: 'test2', + description: 'My preconfigured test 2', + referencedByCount: 1, + isPreconfigured: true, + config: {}, + }, + ] + ); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -217,6 +221,36 @@ describe('actions_connectors_list component with items', () => { expect(wrapper.find('[data-test-subj="preConfiguredTitleMessage"]')).toHaveLength(2); }); + it('supports pagination', async () => { + await setup( + times(15, (index) => ({ + id: `connector${index}`, + actionTypeId: 'test', + name: `My test ${index}`, + secrets: {}, + description: `My test ${index}`, + isPreconfigured: false, + referencedByCount: 1, + config: {}, + })) + ); + expect(wrapper.find('[data-test-subj="actionsTable"]').first().prop('pagination')) + .toMatchInlineSnapshot(` + Object { + "initialPageIndex": 0, + "pageIndex": 0, + } + `); + wrapper.find('[data-test-subj="pagination-button-1"]').first().simulate('click'); + expect(wrapper.find('[data-test-subj="actionsTable"]').first().prop('pagination')) + .toMatchInlineSnapshot(` + Object { + "initialPageIndex": 0, + "pageIndex": 1, + } + `); + }); + test('if select item for edit should render ConnectorEditFlyout', async () => { await setup(); await wrapper.find('[data-test-subj="edit1"]').first().simulate('click'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index ff5585cf04dbe..c5d0a6aae54fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -18,6 +18,7 @@ import { EuiToolTip, EuiButtonIcon, EuiEmptyPrompt, + Criteria, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; @@ -54,6 +55,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [actions, setActions] = useState([]); + const [pageIndex, setPageIndex] = useState(0); const [selectedItems, setSelectedItems] = useState([]); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [isLoadingActions, setIsLoadingActions] = useState(false); @@ -233,7 +235,15 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { : '', })} data-test-subj="actionsTable" - pagination={true} + pagination={{ + initialPageIndex: 0, + pageIndex, + }} + onTableChange={({ page }: Criteria) => { + if (page) { + setPageIndex(page.index); + } + }} selection={ canDelete ? { diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 3187451d2600e..c479359ff7e6e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -21,6 +21,7 @@ export { AlertTypeParamsExpressionProps, ValidationResult, ActionVariable, + ActionVariables, ActionConnector, IErrorObject, } from './types'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index cf7fc9edd9529..41f6b66c30aaf 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -91,7 +91,6 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); expect(typeof response.body.scheduledTaskId).to.be('string'); const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts index 642173a7c2c6c..5ebce8edf6fb7 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts @@ -63,7 +63,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -71,7 +70,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -99,7 +97,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -107,7 +104,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -132,7 +128,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -140,7 +135,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -168,14 +162,12 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('execute'); expect(executionStatus.error.message).to.be('this alert is intended to fail'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); }); it('should eventually have error reason "unknown" when appropriate', async () => { @@ -191,7 +183,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); let executionStatus = await waitForStatus(alertId, new Set(['ok'])); @@ -210,7 +201,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('unknown'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); const message = 'params invalid: [param1]: expected value of type [string] but got [number]'; expect(executionStatus.error.message).to.be(message); @@ -316,18 +306,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon await delay(WaitForStatusIncrement); return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement); } - - async function ensureAlertUpdatedAtHasNotChanged(alertId: string, originalUpdatedAt: string) { - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${alertId}` - ); - const { updatedAt, executionStatus } = response.body; - expect(Date.parse(updatedAt)).to.be.greaterThan(0); - expect(Date.parse(updatedAt)).to.eql(Date.parse(originalUpdatedAt)); - expect(Date.parse(executionStatus.lastExecutionDate)).to.be.greaterThan( - Date.parse(originalUpdatedAt) - ); - } } function expectErrorExecutionStatus(executionStatus: Record, startDate: number) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index bd6afacf206d9..17070a14069ce 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -82,14 +82,5 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, ]); }); - - it('7.11.0 migrates alerts to contain `updatedAt` field', async () => { - const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` - ); - - expect(response.status).to.eql(200); - expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); - }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 6f410add0fa4d..f59b79a6b7bfc 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -9,15 +9,12 @@ import { settingsObjectId, settingsObjectType, } from '../../../../../plugins/uptime/server/lib/saved_objects'; -import { registerMochaHooksForSnapshots } from '../../../../apm_api_integration/common/match_snapshot'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const server = getService('kibanaServer'); describe('uptime REST endpoints', () => { - registerMochaHooksForSnapshots(); - beforeEach('clear settings', async () => { try { await server.savedObjects.delete({ diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts index 08a339ed59326..bdc18ac831d27 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts @@ -9,7 +9,6 @@ import { isRight } from 'fp-ts/lib/Either'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { MonitorSummariesResultType } from '../../../../../plugins/uptime/common/runtime_types'; import { API_URLS } from '../../../../../plugins/uptime/common/constants'; -import { expectSnapshot } from '../../../../apm_api_integration/common/match_snapshot'; interface ExpectedMonitorStatesPage { response: any; diff --git a/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts b/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts index 0a730217e53f5..751ee8753c449 100644 --- a/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts +++ b/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts @@ -9,7 +9,6 @@ import { format } from 'url'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts b/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts index 0cfdf3ec474d5..3cf1c2cecb42b 100644 --- a/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts +++ b/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts @@ -9,7 +9,6 @@ import { format } from 'url'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts index 39dd721c7067e..0381e5f51bb9b 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registerMochaHooksForSnapshots } from '../../common/match_snapshot'; export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('APM specs (basic)', function () { - registerMochaHooksForSnapshots(); - this.tags('ciGroup1'); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts index cae562b3f5dc5..d52aa2727d651 100644 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts @@ -8,7 +8,6 @@ import { first } from 'lodash'; import { MetricsChartsByAgentAPIResponse } from '../../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; import { GenericMetricsChart } from '../../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; interface ChartResponse { body: MetricsChartsByAgentAPIResponse; diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts index 01fa09630e85a..cdeab9ecbdc49 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts index d729680154c1d..3820a76651053 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts index b699a30d40418..088b7cb8bb568 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { pick, uniqBy } from 'lodash'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import archives from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts index cd2bdb7fde19e..4d70c4e949433 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import { isEmpty, pick } from 'lodash'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts index 1221ce0198d82..40b6db6997f8a 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts index 32a06b8fb880e..bde9364efc685 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { omit, orderBy } from 'lodash'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { AgentConfigurationIntake } from '../../../../../plugins/apm/common/agent_configuration/configuration_types'; import { AgentConfigSearchParams } from '../../../../../plugins/apm/server/routes/settings/agent_configuration'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts index 2c8f13ce79f76..a9e6eae8bed88 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts index d1dbd15f4dced..4fa3e46430e91 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts index 60b4020e73dce..8ac5566fc2c49 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts index 6a3a1ddd0f6ae..4dbd6cc4cd6f7 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts index f2e58718870bf..24f542c222d6e 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts index e0b03e1a91f40..a93aff5c8cf32 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { isEmpty } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts index 86309c91b0bc2..da3d07a0e83a3 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts index 2e802957a95e3..d4fdfe6d0fc76 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; function sortTransactionGroups(items: any[]) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts index c3b969d765664..5ebbdfa16d9a8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts index 6235e7abd37ec..05c6439508ece 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts index 12fdb5ba9704e..f2033e03f5821 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts index 0edffe7999a65..6fc8cb4c1d4e1 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts index 518c4ef8a81a7..6db5de24baa99 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts index ca5670d41d8ee..5d910862843d5 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts index c887fa3e77648..961c783434639 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts index 5dbe266deeb81..7e970493eb611 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index a67dd1bcbd7a8..97ab662313c7c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -5,14 +5,11 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { registerMochaHooksForSnapshots } from '../../common/match_snapshot'; export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('APM specs (trial)', function () { this.tags('ciGroup1'); - registerMochaHooksForSnapshots(); - describe('Services', function () { loadTestFile(require.resolve('./services/annotations')); loadTestFile(require.resolve('./services/top_services.ts')); diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts index 9c01833f78e5d..b1e29b220dd5c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { isEmpty, uniq } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts index bb611013351d7..90cad966ba102 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts b/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts index 47e465596e0d7..99e90b8433c84 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts +++ b/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/mocha_decorations.d.ts b/x-pack/test/mocha_decorations.d.ts deleted file mode 100644 index 44f43a22de1f9..0000000000000 --- a/x-pack/test/mocha_decorations.d.ts +++ /dev/null @@ -1,17 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Suite } from 'mocha'; - -// We need to use the namespace here to match the Mocha definition -declare module 'mocha' { - interface Suite { - /** - * Assign tags to the test suite to determine in which CI job it should be run. - */ - tags(tags: string[] | string): void; - } -} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e041292ebf3c9..3ac7026d16a17 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -5,7 +5,7 @@ "incremental": false, "types": ["mocha", "node", "flot"] }, - "include": ["**/*", "../typings/**/*"], + "include": ["**/*", "../typings/**/*", "../../packages/kbn-test/types/ftr_globals/**/*"], "exclude": ["../typings/jest.d.ts"], "references": [ { "path": "../../src/core/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 2a82e7024a895..9be39ea18e3d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18798,24 +18798,24 @@ livereload-js@^2.3.0: resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== -lmdb-store-0.9@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/lmdb-store-0.9/-/lmdb-store-0.9-0.7.2.tgz#45b907a46d0a676fee955629bd2f70f06efb26bb" - integrity sha512-/MO8G6p4l7ku1ltCCdE/2ZOtSQBSM0B02vIemMHjoKgjE/fooanJYXIFwtZYM5r/hBDxmO+L3q5ASAXbfHQ6pQ== +lmdb-store-0.9@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/lmdb-store-0.9/-/lmdb-store-0.9-0.7.3.tgz#c2cb27dfa916ab966cceed692c67e4236813104a" + integrity sha512-t8iCnN6T3NZPFelPmjYIjCg+nhGbOdc0xcHCG40v01AWRTN49OINSt2k/u+16/2/HrI+b6Ssb8WByXUhbyHz6w== dependencies: fs-extra "^9.0.1" msgpackr "^0.5.3" nan "^2.14.1" node-gyp-build "^4.2.3" - weak-lru-cache "^0.2.0" + weak-lru-cache "^0.3.9" -lmdb-store@^0.8.11: - version "0.8.11" - resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.8.11.tgz#7f7c756a115ceab32c51c0948444bfd5d1716ab3" - integrity sha512-CFgxh2/TL1NXyJ8FQPXY50O/gADxih7Gx0RjKRScGlyxihqSLd/fGzMkbvDdeAOAS8bsnYpLojAMTSeNiwN8dQ== +lmdb-store@^0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.8.15.tgz#4efb0341c2df505dd6f3a7f26f834f0a142a80a2" + integrity sha512-4Q0WZh2FmcJC6esZRUWMfkCmNiz0WU9cOgrxt97ZMTnVfHyOdZhtrt0oOF5EQPfetxxJf/BorKY28aX92R6G6g== dependencies: fs-extra "^9.0.1" - lmdb-store-0.9 "0.7.2" + lmdb-store-0.9 "0.7.3" msgpackr "^0.5.4" nan "^2.14.1" node-gyp-build "^4.2.3" @@ -29141,11 +29141,6 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weak-lru-cache@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.2.0.tgz#447379ccff6dfda1b7a9566c9ef168260be859d1" - integrity sha512-M1l5CzKvM7maa7tCbtL0NW6sOnp8gqup853+9Aq7GL0XNWKNnFOkeE3v3Z5X2IeMzedPwQyPbi4RlFvD6rxs7A== - weak-lru-cache@^0.3.9: version "0.3.9" resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.3.9.tgz#9e56920d4115e8542625d8ef8cc278cbd97f7624"