From 3e211e938bc4c110fd4ab21e38f2b3f133a546ee Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 9 Nov 2020 05:42:45 -0500 Subject: [PATCH 01/87] [Ingest Manager] Unify install* under installPackage (#82916) ## Summary * Add `installPackage` with `installSource` param, to provide a single interface the `install*` functions. ```diff - const res = await installPackageFromRegistry({ + const res = await installPackage({ + installSource: 'registry', ``` and ```diff - const res = await installPackageByUpload({ + const res = await installPackage({ + installSource: 'upload', ``` * Push some repeated work (`install`, `removable`) from `install*` into `_installPackage`. Which also simplifies its interface. ### installPackage For now `installPackage` checks the `installSource` and calls the same `install*` functions to prevent any change in behavior but there's still a lot of overlap between `installPackageFromRegistry` & `installPackageByUpload`. I think we can bring them together into `installPackage` using the same branching on `installSource`. ### local checks with curl
curl request/responses for happy path: ``` ## zip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"response":[{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"}]} ## Uploaded packages can be deleted as expected: curl -X DELETE -u elastic:changeme http://localhost:5601/api/fleet/epm/packages/apache-0.1.4 -H 'kbn-xsrf: xxx' {"response":[{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"}]} ## Now upload curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz -H 'kbn-xsrf: xyz' -H 'Content-Type: application/gzip' {"response":[{"id":"apache-Metrics-Apache-HTTPD-server-status-ecs","type":"dashboard"},{"id":"apache-Logs-Apache-Dashboard-ecs","type":"dashboard"},{"id":"Apache-access-unique-IPs-map-ecs","type":"visualization"},{"id":"Apache-HTTPD-CPU-ecs","type":"visualization"},{"id":"Apache-HTTPD-Load1-slash-5-slash-15-ecs","type":"visualization"},{"id":"Apache-response-codes-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-Workers-ecs","type":"visualization"},{"id":"Apache-HTTPD-Hostname-list-ecs","type":"visualization"},{"id":"Apache-error-logs-over-time-ecs","type":"visualization"},{"id":"Apache-HTTPD-Scoreboard-ecs","type":"visualization"},{"id":"Apache-HTTPD-Uptime-ecs","type":"visualization"},{"id":"Apache-operating-systems-ecs","type":"visualization"},{"id":"Apache-HTTPD-Total-accesses-and-kbytes-ecs","type":"visualization"},{"id":"Apache-browsers-ecs","type":"visualization"},{"id":"Apache-response-codes-of-top-URLs-ecs","type":"visualization"},{"id":"Apache-access-logs-ecs","type":"search"},{"id":"Apache-errors-log-ecs","type":"search"},{"id":"Apache-HTTPD-ecs","type":"search"},{"id":"logs-apache.error-0.1.4","type":"ingest_pipeline"},{"id":"logs-apache.access-0.1.4","type":"ingest_pipeline"},{"id":"logs-apache.error","type":"index_template"},{"id":"metrics-apache.status","type":"index_template"},{"id":"logs-apache.access","type":"index_template"}]} ```
curl request/responses for archive errors: ``` ## Wrong content type: ### tar.gz with application/zip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Error during extraction of package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."} ### zip with application/gzip: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/gzip' {"statusCode":400,"error":"Bad Request","message":"Archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."} ## Invalid packages ### Two top-level directories: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_two_toplevels_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Package contains more than one top-level directory."} ### No manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_no_manifest_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Package must contain a top-level manifest.yml file."} ### Invalid YAML in manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_manifest_invalid_yaml_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Could not parse top-level package manifest: YAMLException: bad indentation of a mapping entry at line 2, column 7:\n name: apache\n ^."} ### Mandatory field missing in manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_manifest_missing_field_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version"} ### Top-level directory doesn't match name and version from manifest: curl -X POST -u elastic:changeme http://localhost:5601/api/fleet/epm/packages --data-binary @$KIBANA_HOME/x-pack/test/ingest_manager_api_integration/apis/fixtures/direct_upload_packages/apache_invalid_toplevel_mismatch_0.1.4.zip -H 'kbn-xsrf: xyz' -H 'Content-Type: application/zip' {"statusCode":400,"error":"Bad Request","message":"Name thisIsATypo and version 0.1.4 do not match top-level directory apache-0.1.4"} ```
#### TS type check examples on `installPackage`
screenshots Screen Shot 2020-11-08 at 4 00 14 PM Screen Shot 2020-11-08 at 4 00 21 PM Screen Shot 2020-11-08 at 4 01 06 PM Screen Shot 2020-11-08 at 4 01 25 PM Screen Shot 2020-11-08 at 4 02 54 PM
### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../server/routes/epm/handlers.ts | 9 +- .../epm/packages/_install_package.test.ts | 4 - .../services/epm/packages/_install_package.ts | 11 +-- .../server/services/epm/packages/index.ts | 3 +- .../server/services/epm/packages/install.ts | 92 ++++++++++++------- 5 files changed, 68 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index 198a54ca84125..1d221b8b1eead 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -35,8 +35,7 @@ import { getPackageInfo, handleInstallPackageFailure, isBulkInstallError, - installPackageFromRegistry, - installPackageByUpload, + installPackage, removeInstallation, getLimitedPackages, getInstallationObject, @@ -149,7 +148,8 @@ export const installPackageFromRegistryHandler: RequestHandler< const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); try { - const res = await installPackageFromRegistry({ + const res = await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey, callCluster, @@ -224,7 +224,8 @@ export const installPackageByUploadHandler: RequestHandler< const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); try { - const res = await installPackageByUpload({ + const res = await installPackage({ + installSource: 'upload', savedObjectsClient, callCluster, archiveBuffer, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts index 5d3e8e9ce87d1..b7650d10b6b25 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts @@ -61,11 +61,7 @@ describe('_installPackage', () => { const installationPromise = _installPackage({ savedObjectsClient: soClient, callCluster, - pkgName: 'abc', - pkgVersion: '1.2.3', paths: [], - removable: false, - internal: false, packageInfo: { name: 'xyz', version: '4.5.6', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts index f570984cc61aa..a83d9428b7c93 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts @@ -21,6 +21,7 @@ import { installPipelines, deletePreviousPipelines } from '../elasticsearch/inge import { installILMPolicy } from '../elasticsearch/ilm/install'; import { installKibanaAssets, getKibanaAssets } from '../kibana/assets/install'; import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; +import { isRequiredPackage } from './index'; import { deleteKibanaSavedObjectsAssets } from './remove'; import { installTransform } from '../elasticsearch/transform/install'; import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install'; @@ -32,28 +33,22 @@ import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './insta export async function _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo, installType, installSource, }: { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; - pkgName: string; - pkgVersion: string; installedPkg?: SavedObject; paths: string[]; - removable: boolean; - internal: boolean; packageInfo: InstallablePackage; installType: InstallType; installSource: InstallSource; }): Promise { + const { internal = false, name: pkgName, version: pkgVersion } = packageInfo; + const removable = !isRequiredPackage(pkgName); const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); // add the package installation to the saved object. // if some installation already exists, just update install info diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts index 410a9c0b22537..a1128011d81e6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts @@ -29,8 +29,7 @@ export { BulkInstallResponse, IBulkInstallPackageError, handleInstallPackageFailure, - installPackageFromRegistry, - installPackageByUpload, + installPackage, ensureInstalledPackage, } from './install'; export { removeInstallation } from './remove'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index e7d8c8d4695d4..00a5c689e906d 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -24,7 +24,6 @@ import * as Registry from '../registry'; import { getInstallation, getInstallationObject, - isRequiredPackage, bulkInstallPackages, isBulkInstallError, } from './index'; @@ -52,7 +51,7 @@ export async function installLatestPackage(options: { name: latestPackage.name, version: latestPackage.version, }); - return installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + return installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }); } catch (err) { throw err; } @@ -148,7 +147,8 @@ export async function handleInstallPackageFailure({ } const prevVersion = `${pkgName}-${installedPkg.attributes.version}`; logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`); - await installPackageFromRegistry({ + await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey: prevVersion, callCluster, @@ -186,7 +186,12 @@ export async function upgradePackage({ }); try { - const assets = await installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + const assets = await installPackage({ + installSource: 'registry', + savedObjectsClient, + pkgkey, + callCluster, + }); return { name: pkgToUpgrade, newVersion: latestPkg.version, @@ -218,19 +223,19 @@ export async function upgradePackage({ } } -interface InstallPackageParams { +interface InstallRegistryPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; callCluster: CallESAsCurrentUser; force?: boolean; } -export async function installPackageFromRegistry({ +async function installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster, force = false, -}: InstallPackageParams): Promise { +}: InstallRegistryPackageParams): Promise { // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge @@ -250,37 +255,36 @@ export async function installPackageFromRegistry({ const { paths, registryPackageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); - const removable = !isRequiredPackage(pkgName); - const { internal = false } = registryPackageInfo; - const installSource = 'registry'; - return _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo: registryPackageInfo, installType, - installSource, + installSource: 'registry', }); } -export async function installPackageByUpload({ - savedObjectsClient, - callCluster, - archiveBuffer, - contentType, -}: { +interface InstallUploadedArchiveParams { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; archiveBuffer: Buffer; contentType: string; -}): Promise { +} + +export type InstallPackageParams = + | ({ installSource: Extract } & InstallRegistryPackageParams) + | ({ installSource: Extract } & InstallUploadedArchiveParams); + +async function installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, +}: InstallUploadedArchiveParams): Promise { const { paths, archivePackageInfo } = await loadArchivePackage({ archiveBuffer, contentType }); + const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName: archivePackageInfo.name, @@ -292,25 +296,45 @@ export async function installPackageByUpload({ ); } - const removable = !isRequiredPackage(archivePackageInfo.name); - const { internal = false } = archivePackageInfo; - const installSource = 'upload'; - return _installPackage({ savedObjectsClient, callCluster, - pkgName: archivePackageInfo.name, - pkgVersion: archivePackageInfo.version, installedPkg, paths, - removable, - internal, packageInfo: archivePackageInfo, installType, - installSource, + installSource: 'upload', }); } +export async function installPackage(args: InstallPackageParams) { + if (!('installSource' in args)) { + throw new Error('installSource is required'); + } + + if (args.installSource === 'registry') { + const { savedObjectsClient, pkgkey, callCluster, force } = args; + + return installPackageFromRegistry({ + savedObjectsClient, + pkgkey, + callCluster, + force, + }); + } else if (args.installSource === 'upload') { + const { savedObjectsClient, callCluster, archiveBuffer, contentType } = args; + + return installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, + }); + } + // @ts-expect-error s/b impossibe b/c `never` by this point, but just in case + throw new Error(`Unknown installSource: ${args.installSource}`); +} + export const updateVersion = async ( savedObjectsClient: SavedObjectsClientContract, pkgName: string, @@ -421,7 +445,9 @@ export async function ensurePackagesCompletedInstall( const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; // reinstall package if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { - acc.push(installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster })); + acc.push( + installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }) + ); } return acc; }, []); From fdc18392ad2c70b92fbfb4f69e1af892c020efab Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 9 Nov 2020 11:51:14 +0100 Subject: [PATCH 02/87] SavedObjects search_dsl: add match_phrase_prefix clauses when using prefix search (#82693) * add match_phrase_prefix clauses when using prefix search * add FTR tests --- .../lib/search_dsl/query_params.test.ts | 473 ++++++++++++------ .../service/lib/search_dsl/query_params.ts | 189 +++++-- .../service/lib/search_dsl/search_dsl.test.ts | 1 - .../service/lib/search_dsl/search_dsl.ts | 1 - .../apis/saved_objects/find.js | 64 +++ .../saved_objects/find_edgecases/data.json | 93 ++++ .../find_edgecases/mappings.json | 267 ++++++++++ 7 files changed, 883 insertions(+), 205 deletions(-) create mode 100644 test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json create mode 100644 test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 333f5caf72525..a8c5df8d64630 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -21,28 +21,64 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { typeRegistryMock } from '../../../saved_objects_type_registry.mock'; +import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING } from '../utils'; import { getQueryParams, getClauseForReference } from './query_params'; -const registry = typeRegistryMock.create(); +const registerTypes = (registry: SavedObjectTypeRegistry) => { + registry.registerType({ + name: 'pending', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { title: { type: 'text' } }, + }, + management: { + defaultSearchField: 'title', + }, + }); -const MAPPINGS = { - properties: { - pending: { properties: { title: { type: 'text' } } }, - saved: { + registry.registerType({ + name: 'saved', + hidden: false, + namespaceType: 'single', + mappings: { properties: { title: { type: 'text', fields: { raw: { type: 'keyword' } } }, obj: { properties: { key1: { type: 'text' } } }, }, }, - // mock registry returns isMultiNamespace=true for 'shared' type - shared: { properties: { name: { type: 'keyword' } } }, - // mock registry returns isNamespaceAgnostic=true for 'global' type - global: { properties: { name: { type: 'keyword' } } }, - }, + management: { + defaultSearchField: 'title', + }, + }); + + registry.registerType({ + name: 'shared', + hidden: false, + namespaceType: 'multiple', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); + + registry.registerType({ + name: 'global', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); }; -const ALL_TYPES = Object.keys(MAPPINGS.properties); + +const ALL_TYPES = ['pending', 'saved', 'shared', 'global']; // get all possible subsets (combination) of all types const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( (subsets, value) => subsets.concat(subsets.map((set) => [...set, value])), @@ -51,48 +87,53 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( .filter((x) => x.length) // exclude empty set .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it -const createTypeClause = (type: string, namespaces?: string[]) => { - if (registry.isMultiNamespace(type)) { - const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; - return { - bool: { - must: expect.arrayContaining([{ terms: { namespaces: array } }]), - must_not: [{ exists: { field: 'namespace' } }], - }, - }; - } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; - const should: any = []; - if (nonDefaultNamespaces.length > 0) { - should.push({ terms: { namespace: nonDefaultNamespaces } }); - } - if (namespaces?.includes('default')) { - should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); - } - return { - bool: { - must: [{ term: { type } }], - should: expect.arrayContaining(should), - minimum_should_match: 1, - must_not: [{ exists: { field: 'namespaces' } }], - }, - }; - } - // isNamespaceAgnostic - return { - bool: expect.objectContaining({ - must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], - }), - }; -}; - /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake */ describe('#getQueryParams', () => { - const mappings = MAPPINGS; + let registry: SavedObjectTypeRegistry; type Result = ReturnType; + beforeEach(() => { + registry = new SavedObjectTypeRegistry(); + registerTypes(registry); + }); + + const createTypeClause = (type: string, namespaces?: string[]) => { + if (registry.isMultiNamespace(type)) { + const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; + return { + bool: { + must: expect.arrayContaining([{ terms: { namespaces: array } }]), + must_not: [{ exists: { field: 'namespace' } }], + }, + }; + } else if (registry.isSingleNamespace(type)) { + const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const should: any = []; + if (nonDefaultNamespaces.length > 0) { + should.push({ terms: { namespace: nonDefaultNamespaces } }); + } + if (namespaces?.includes('default')) { + should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); + } + return { + bool: { + must: [{ term: { type } }], + should: expect.arrayContaining(should), + minimum_should_match: 1, + must_not: [{ exists: { field: 'namespaces' } }], + }, + }; + } + // isNamespaceAgnostic + return { + bool: expect.objectContaining({ + must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], + }), + }; + }; + describe('kueryNode filter clause', () => { const expectResult = (result: Result, expected: any) => { expect(result.query.bool.filter).toEqual(expect.arrayContaining([expected])); @@ -100,13 +141,13 @@ describe('#getQueryParams', () => { describe('`kueryNode` parameter', () => { it('does not include the clause when `kueryNode` is not specified', () => { - const result = getQueryParams({ mappings, registry, kueryNode: undefined }); + const result = getQueryParams({ registry, kueryNode: undefined }); expect(result.query.bool.filter).toHaveLength(1); }); it('includes the specified Kuery clause', () => { const test = (kueryNode: KueryNode) => { - const result = getQueryParams({ mappings, registry, kueryNode }); + const result = getQueryParams({ registry, kueryNode }); const expected = esKuery.toElasticsearchQuery(kueryNode); expect(result.query.bool.filter).toHaveLength(2); expectResult(result, expected); @@ -165,7 +206,6 @@ describe('#getQueryParams', () => { it('does not include the clause when `hasReference` is not specified', () => { const result = getQueryParams({ - mappings, registry, hasReference: undefined, }); @@ -176,7 +216,6 @@ describe('#getQueryParams', () => { it('creates a should clause for specified reference when operator is `OR`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -192,7 +231,6 @@ describe('#getQueryParams', () => { it('creates a must clause for specified reference when operator is `AND`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -210,7 +248,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -229,7 +266,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -244,7 +280,6 @@ describe('#getQueryParams', () => { it('defaults to `OR` when operator is not specified', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, }); @@ -278,14 +313,13 @@ describe('#getQueryParams', () => { }; it('searches for all known types when `type` is not specified', () => { - const result = getQueryParams({ mappings, registry, type: undefined }); + const result = getQueryParams({ registry, type: undefined }); expectResult(result, ...ALL_TYPES); }); it('searches for specified type/s', () => { const test = (typeOrTypes: string | string[]) => { const result = getQueryParams({ - mappings, registry, type: typeOrTypes, }); @@ -309,18 +343,17 @@ describe('#getQueryParams', () => { const test = (namespaces?: string[]) => { for (const typeOrTypes of ALL_TYPE_SUBSETS) { - const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespaces }); + const result = getQueryParams({ registry, type: typeOrTypes, namespaces }); const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; expectResult(result, ...types.map((x) => createTypeClause(x, namespaces))); } // also test with no specified type/s - const result = getQueryParams({ mappings, registry, type: undefined, namespaces }); + const result = getQueryParams({ registry, type: undefined, namespaces }); expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces))); }; it('normalizes and deduplicates provided namespaces', () => { const result = getQueryParams({ - mappings, registry, search: '*', namespaces: ['foo', '*', 'foo', 'bar', 'default'], @@ -360,7 +393,6 @@ describe('#getQueryParams', () => { it('supersedes `type` and `namespaces` parameters', () => { const result = getQueryParams({ - mappings, registry, type: ['pending', 'saved', 'shared', 'global'], namespaces: ['foo', 'bar', 'default'], @@ -381,148 +413,266 @@ describe('#getQueryParams', () => { }); }); - describe('search clause (query.bool.must.simple_query_string)', () => { - const search = 'foo*'; + describe('search clause (query.bool)', () => { + describe('when using simple search (query.bool.must.simple_query_string)', () => { + const search = 'foo'; - const expectResult = (result: Result, sqsClause: any) => { - expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); - }; + const expectResult = (result: Result, sqsClause: any) => { + expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); + }; - describe('`search` parameter', () => { - it('does not include clause when `search` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search: undefined, + describe('`search` parameter', () => { + it('does not include clause when `search` is not specified', () => { + const result = getQueryParams({ + registry, + search: undefined, + }); + expect(result.query.bool.must).toBeUndefined(); }); - expect(result.query.bool.must).toBeUndefined(); - }); - it('creates a clause with query for specified search', () => { - const result = getQueryParams({ - mappings, - registry, - search, + it('creates a clause with query for specified search', () => { + const result = getQueryParams({ + registry, + search, + }); + expectResult(result, expect.objectContaining({ query: search })); }); - expectResult(result, expect.objectContaining({ query: search })); }); - }); - describe('`searchFields` and `rootSearchFields` parameters', () => { - const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { - const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); - }; + describe('`searchFields` and `rootSearchFields` parameters', () => { + const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { + const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; + return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); + }; - const test = ({ - searchFields, - rootSearchFields, - }: { - searchFields?: string[]; - rootSearchFields?: string[]; - }) => { - for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const test = ({ + searchFields, + rootSearchFields, + }: { + searchFields?: string[]; + rootSearchFields?: string[]; + }) => { + for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const result = getQueryParams({ + registry, + type: typeOrTypes, + search, + searchFields, + rootSearchFields, + }); + let fields = rootSearchFields || []; + if (searchFields) { + fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + } + expectResult(result, expect.objectContaining({ fields })); + } + // also test with no specified type/s const result = getQueryParams({ - mappings, registry, - type: typeOrTypes, + type: undefined, search, searchFields, rootSearchFields, }); let fields = rootSearchFields || []; if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); } expectResult(result, expect.objectContaining({ fields })); - } - // also test with no specified type/s - const result = getQueryParams({ - mappings, - registry, - type: undefined, - search, - searchFields, - rootSearchFields, + }; + + it('throws an error if a raw search field contains a "." character', () => { + expect(() => + getQueryParams({ + registry, + type: undefined, + search, + searchFields: undefined, + rootSearchFields: ['foo', 'bar.baz'], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` + ); }); - let fields = rootSearchFields || []; - if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); - } - expectResult(result, expect.objectContaining({ fields })); - }; - it('throws an error if a raw search field contains a "." character', () => { - expect(() => - getQueryParams({ - mappings, + it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { + const result = getQueryParams({ registry, - type: undefined, search, searchFields: undefined, - rootSearchFields: ['foo', 'bar.baz'], - }) - ).toThrowErrorMatchingInlineSnapshot( - `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` - ); + rootSearchFields: undefined, + }); + expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); + }); + + it('includes specified search fields for appropriate type/s', () => { + test({ searchFields: ['title'] }); + }); + + it('supports boosting', () => { + test({ searchFields: ['title^3'] }); + }); + + it('supports multiple search fields', () => { + test({ searchFields: ['title, title.raw'] }); + }); + + it('includes specified raw search fields', () => { + test({ rootSearchFields: ['_id'] }); + }); + + it('supports multiple raw search fields', () => { + test({ rootSearchFields: ['_id', 'originId'] }); + }); + + it('supports search fields and raw search fields', () => { + test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + }); }); - it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { - const result = getQueryParams({ - mappings, + describe('`defaultSearchOperator` parameter', () => { + it('does not include default_operator when `defaultSearchOperator` is not specified', () => { + const result = getQueryParams({ + registry, + search, + defaultSearchOperator: undefined, + }); + expectResult( + result, + expect.not.objectContaining({ default_operator: expect.anything() }) + ); + }); + + it('includes specified default operator', () => { + const defaultSearchOperator = 'AND'; + const result = getQueryParams({ + registry, + search, + defaultSearchOperator, + }); + expectResult( + result, + expect.objectContaining({ default_operator: defaultSearchOperator }) + ); + }); + }); + }); + + describe('when using prefix search (query.bool.should)', () => { + const searchQuery = 'foo*'; + + const getQueryParamForSearch = ({ + search, + searchFields, + type, + }: { + search?: string; + searchFields?: string[]; + type?: string[]; + }) => + getQueryParams({ registry, search, - searchFields: undefined, - rootSearchFields: undefined, + searchFields, + type, }); - expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); - }); - it('includes specified search fields for appropriate type/s', () => { - test({ searchFields: ['title'] }); - }); + it('uses a `should` clause instead of `must`', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); - it('supports boosting', () => { - test({ searchFields: ['title^3'] }); + expect(result.query.bool.must).toBeUndefined(); + expect(result.query.bool.should).toEqual(expect.any(Array)); + expect(result.query.bool.should.length).toBeGreaterThanOrEqual(1); + expect(result.query.bool.minimum_should_match).toBe(1); }); - - it('supports multiple search fields', () => { - test({ searchFields: ['title, title.raw'] }); + it('includes the `simple_query_string` in the `should` clauses', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); + expect(result.query.bool.should[0]).toEqual({ + simple_query_string: expect.objectContaining({ + query: searchQuery, + }), + }); }); - it('includes specified raw search fields', () => { - test({ rootSearchFields: ['_id'] }); + it('adds a should clause for each `searchFields` / `type` tuple', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title', 'desc'], + type: ['saved', 'pending'], + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'pending.title', 'saved.desc', 'pending.desc']); }); - it('supports multiple raw search fields', () => { - test({ rootSearchFields: ['_id', 'originId'] }); + it('uses all registered types when `type` is not provided', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: undefined, + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['pending.title', 'saved.title', 'shared.title', 'global.title']); }); - it('supports search fields and raw search fields', () => { - test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + it('removes the prefix search wildcard from the query', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: ['saved'], + }); + const shouldClauses = result.query.bool.should; + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses[0].match_phrase_prefix['saved.title'].query).toEqual('foo'); }); - }); - describe('`defaultSearchOperator` parameter', () => { - it('does not include default_operator when `defaultSearchOperator` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator: undefined, + it("defaults to the type's default search field when `searchFields` is not specified", () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: undefined, + type: ['saved', 'global'], }); - expectResult(result, expect.not.objectContaining({ default_operator: expect.anything() })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'global.name']); }); - it('includes specified default operator', () => { - const defaultSearchOperator = 'AND'; - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator, + it('supports boosting', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title^3', 'description'], + type: ['saved'], }); - expectResult(result, expect.objectContaining({ default_operator: defaultSearchOperator })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses.map((clause: any) => clause.match_phrase_prefix)).toEqual([ + { 'saved.title': { query: 'foo', boost: 3 } }, + { 'saved.description': { query: 'foo', boost: 1 } }, + ]); }); }); }); @@ -532,7 +682,6 @@ describe('#getQueryParams', () => { it(`throws for ${type} when namespaces is an empty array`, () => { expect(() => getQueryParams({ - mappings, registry, namespaces: [], }) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 8d4fe13b9bede..f73777c4f454f 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -20,7 +20,6 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; @@ -28,22 +27,17 @@ import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; * Gets the types based on the type. Uses mappings to support * null type (all types), a single type string or an array */ -function getTypes(mappings: IndexMapping, type?: string | string[]) { +function getTypes(registry: ISavedObjectTypeRegistry, type?: string | string[]) { if (!type) { - return Object.keys(getRootPropertiesObjects(mappings)); + return registry.getAllTypes().map((registeredType) => registeredType.name); } - - if (Array.isArray(type)) { - return type; - } - - return [type]; + return Array.isArray(type) ? type : [type]; } /** * Get the field params based on the types, searchFields, and rootSearchFields */ -function getFieldsForTypes( +function getSimpleQueryStringTypeFields( types: string[], searchFields: string[] = [], rootSearchFields: string[] = [] @@ -130,7 +124,6 @@ export interface HasReferenceQueryParams { export type SearchOperator = 'AND' | 'OR'; interface QueryParams { - mappings: IndexMapping; registry: ISavedObjectTypeRegistry; namespaces?: string[]; type?: string | string[]; @@ -188,11 +181,26 @@ export function getClauseForReference(reference: HasReferenceQueryParams) { }; } +// A de-duplicated set of namespaces makes for a more efficient query. +// +// Additionally, we treat the `*` namespace as the `default` namespace. +// In the Default Distribution, the `*` is automatically expanded to include all available namespaces. +// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` +// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, +// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place +// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. +// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 +const normalizeNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize + ? Array.from( + new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) + ) + : undefined; + /** * Get the "query" related keys for the search body */ export function getQueryParams({ - mappings, registry, namespaces, type, @@ -206,7 +214,7 @@ export function getQueryParams({ kueryNode, }: QueryParams) { const types = getTypes( - mappings, + registry, typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type ); @@ -214,28 +222,10 @@ export function getQueryParams({ hasReference = [hasReference]; } - // A de-duplicated set of namespaces makes for a more effecient query. - // - // Additonally, we treat the `*` namespace as the `default` namespace. - // In the Default Distribution, the `*` is automatically expanded to include all available namespaces. - // However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` - // to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, - // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place - // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. - // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 - const normalizeNamespaces = (namespacesToNormalize?: string[]) => - namespacesToNormalize - ? Array.from( - new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) - ) - : undefined; - const bool: any = { filter: [ ...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []), - ...(hasReference && hasReference.length - ? [getReferencesFilter(hasReference, hasReferenceOperator)] - : []), + ...(hasReference?.length ? [getReferencesFilter(hasReference, hasReferenceOperator)] : []), { bool: { should: types.map((shouldType) => { @@ -251,16 +241,133 @@ export function getQueryParams({ }; if (search) { - bool.must = [ - { - simple_query_string: { - query: search, - ...getFieldsForTypes(types, searchFields, rootSearchFields), - ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), - }, - }, - ]; + const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search); + const simpleQueryStringClause = getSimpleQueryStringClause({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, + }); + + if (useMatchPhrasePrefix) { + bool.should = [ + simpleQueryStringClause, + ...getMatchPhrasePrefixClauses({ search, searchFields, types, registry }), + ]; + bool.minimum_should_match = 1; + } else { + bool.must = [simpleQueryStringClause]; + } } return { query: { bool } }; } + +// we only want to add match_phrase_prefix clauses +// if the search is a prefix search +const shouldUseMatchPhrasePrefix = (search: string): boolean => { + return search.trim().endsWith('*'); +}; + +const getMatchPhrasePrefixClauses = ({ + search, + searchFields, + registry, + types, +}: { + search: string; + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}) => { + // need to remove the prefix search operator + const query = search.replace(/[*]$/, ''); + const mppFields = getMatchPhrasePrefixFields({ searchFields, types, registry }); + return mppFields.map(({ field, boost }) => { + return { + match_phrase_prefix: { + [field]: { + query, + boost, + }, + }, + }; + }); +}; + +interface FieldWithBoost { + field: string; + boost?: number; +} + +const getMatchPhrasePrefixFields = ({ + searchFields = [], + types, + registry, +}: { + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}): FieldWithBoost[] => { + const output: FieldWithBoost[] = []; + + searchFields = searchFields.filter((field) => field !== '*'); + let fields: string[]; + if (searchFields.length === 0) { + fields = types.reduce((typeFields, type) => { + const defaultSearchField = registry.getType(type)?.management?.defaultSearchField; + if (defaultSearchField) { + return [...typeFields, `${type}.${defaultSearchField}`]; + } + return typeFields; + }, [] as string[]); + } else { + fields = []; + for (const field of searchFields) { + fields = fields.concat(types.map((type) => `${type}.${field}`)); + } + } + + fields.forEach((rawField) => { + const [field, rawBoost] = rawField.split('^'); + let boost: number = 1; + if (rawBoost) { + try { + boost = parseInt(rawBoost, 10); + } catch (e) { + boost = 1; + } + } + if (isNaN(boost)) { + boost = 1; + } + output.push({ + field, + boost, + }); + }); + return output; +}; + +const getSimpleQueryStringClause = ({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, +}: { + search: string; + types: string[]; + searchFields?: string[]; + rootSearchFields?: string[]; + defaultSearchOperator?: SearchOperator; +}) => { + return { + simple_query_string: { + query: search, + ...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields), + ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), + }, + }; +}; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index a9f26f71a3f2b..3522ab9ef1736 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -76,7 +76,6 @@ describe('getSearchDsl', () => { getSearchDsl(mappings, registry, opts); expect(getQueryParams).toHaveBeenCalledTimes(1); expect(getQueryParams).toHaveBeenCalledWith({ - mappings, registry, namespaces: opts.namespaces, type: opts.type, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index d5da82e5617be..bddecc4d7f649 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -71,7 +71,6 @@ export function getSearchDsl( return { ...getQueryParams({ - mappings, registry, namespaces, type, diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index c2e36b4a669ff..e5da46644672b 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -334,6 +334,70 @@ export default function ({ getService }) { }); }); + describe('searching for special characters', () => { + before(() => esArchiver.load('saved_objects/find_edgecases')); + after(() => esArchiver.unload('saved_objects/find_edgecases')); + + it('can search for objects with dashes', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-vis*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search with the prefix search character just after a special one', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search for objects with asterisk', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'some*vi*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['some*visualization']); + })); + + it('can still search tokens by prefix', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'visuali*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql([ + 'my-visualization', + 'some*visualization', + ]); + })); + }); + describe('without kibana index', () => { before( async () => diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json new file mode 100644 index 0000000000000..0c8b35fd3f499 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json @@ -0,0 +1,93 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-dash", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "my-visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-asterisk", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "some*visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-1", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-2", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json new file mode 100644 index 0000000000000..e601c43431437 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json @@ -0,0 +1,267 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "namespace": { + "type": "keyword" + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} From 202dec7c24d14ae2e20be97743089e377daf3047 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 9 Nov 2020 14:17:29 +0300 Subject: [PATCH 03/87] Enable send to background in TSVB (#82835) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/vis_type_timeseries/common/types.ts | 3 ++- .../vis_type_timeseries/common/vis_schema.ts | 1 + .../vis_type_timeseries/public/request_handler.js | 4 +++- .../strategies/abstract_search_strategy.test.js | 8 +++++++- .../strategies/abstract_search_strategy.ts | 13 +++++++++---- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 4520069244527..8973060848b41 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -18,8 +18,9 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { metricsItems, panel, seriesItems } from './vis_schema'; +import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema'; export type SeriesItemsSchema = TypeOf; export type MetricsItemsSchema = TypeOf; export type PanelSchema = TypeOf; +export type VisPayload = TypeOf; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 40f776050617e..27f09fb574b0f 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -273,4 +273,5 @@ export const visPayloadSchema = schema.object({ min: stringRequired, max: stringRequired, }), + sessionId: schema.maybe(schema.string()), }); diff --git a/src/plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js index e33d0e254f609..12b7f3d417ef6 100644 --- a/src/plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.js @@ -32,7 +32,8 @@ export const metricsRequestHandler = async ({ const config = getUISettings(); const timezone = getTimezone(config); const uiStateObj = uiState.get(visParams.type, {}); - const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange); + const dataSearch = getDataStart(); + const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange); const scaledDataFormat = config.get('dateFormat:scaled'); const dateFormat = config.get('dateFormat'); @@ -53,6 +54,7 @@ export const metricsRequestHandler = async ({ panels: [visParams], state: uiStateObj, savedObjectId: savedObjectId || 'unsaved', + sessionId: dataSearch.search.session.getSessionId(), }), }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index 9710f7daf69b6..2c38e883cd69f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -28,6 +28,7 @@ describe('AbstractSearchStrategy', () => { beforeEach(() => { mockedFields = {}; req = { + payload: {}, pre: { indexPatternsService: { getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields), @@ -60,6 +61,9 @@ describe('AbstractSearchStrategy', () => { const responses = await abstractSearchStrategy.search( { + payload: { + sessionId: 1, + }, requestContext: { search: { search: searchFn }, }, @@ -76,7 +80,9 @@ describe('AbstractSearchStrategy', () => { }, indexType: undefined, }, - {} + { + sessionId: 1, + } ); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index eb22fcb1dd689..b1e21edf8b588 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -23,8 +23,10 @@ import { IUiSettingsClient, SavedObjectsClientContract, } from 'kibana/server'; + import { Framework } from '../../../plugin'; import { IndexPatternsFetcher } from '../../../../../data/server'; +import { VisPayload } from '../../../../common/types'; /** * ReqFacade is a regular KibanaRequest object extended with additional service @@ -32,17 +34,17 @@ import { IndexPatternsFetcher } from '../../../../../data/server'; * * This will be replaced by standard KibanaRequest and RequestContext objects in a later version. */ -export type ReqFacade = FakeRequest & { +export interface ReqFacade extends FakeRequest { requestContext: RequestHandlerContext; framework: Framework; - payload: unknown; + payload: T; pre: { indexPatternsService?: IndexPatternsFetcher; }; getUiSettingsService: () => IUiSettingsClient; getSavedObjectsClient: () => SavedObjectsClientContract; getEsShardTimeout: () => Promise; -}; +} export class AbstractSearchStrategy { public indexType?: string; @@ -53,8 +55,10 @@ export class AbstractSearchStrategy { this.additionalParams = additionalParams; } - async search(req: ReqFacade, bodies: any[], options = {}) { + async search(req: ReqFacade, bodies: any[], options = {}) { const requests: any[] = []; + const { sessionId } = req.payload; + bodies.forEach((body) => { requests.push( req.requestContext @@ -67,6 +71,7 @@ export class AbstractSearchStrategy { indexType: this.indexType, }, { + sessionId, ...options, } ) From 6110ef82a3a0537a82738c3f039d850772f24306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 9 Nov 2020 12:43:11 +0100 Subject: [PATCH 04/87] [Logs UI] Fix errors during navigation (#78319) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../containers/logs/log_entries/index.ts | 24 ++++++++---- .../infra/public/utils/use_tracked_promise.ts | 37 ++++++++++++++++--- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 4c8c610794b2e..214bb16b24283 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { useEffect, useState, useReducer, useCallback } from 'react'; +import { useMountedState } from 'react-use'; import createContainer from 'constate'; import { pick, throttle } from 'lodash'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; @@ -146,15 +147,20 @@ const useFetchEntriesEffect = ( props: LogEntriesProps ) => { const { services } = useKibanaContextForPlugin(); + const isMounted = useMountedState(); const [prevParams, cachePrevParams] = useState(); const [startedStreaming, setStartedStreaming] = useState(false); + const dispatchIfMounted = useCallback((action) => (isMounted() ? dispatch(action) : undefined), [ + dispatch, + isMounted, + ]); const runFetchNewEntriesRequest = async (overrides: Partial = {}) => { if (!props.startTimestamp || !props.endTimestamp) { return; } - dispatch({ type: Action.FetchingNewEntries }); + dispatchIfMounted({ type: Action.FetchingNewEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -175,13 +181,15 @@ const useFetchEntriesEffect = ( }; const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ type: Action.ReceiveNewEntries, payload }); + dispatchIfMounted({ type: Action.ReceiveNewEntries, payload }); // Move position to the bottom if it's the first load. // Do it in the next tick to allow the `dispatch` to fire if (!props.timeKey && payload.bottomCursor) { setTimeout(() => { - props.jumpToTargetPosition(payload.bottomCursor!); + if (isMounted()) { + props.jumpToTargetPosition(payload.bottomCursor!); + } }); } else if ( props.timeKey && @@ -192,7 +200,7 @@ const useFetchEntriesEffect = ( props.jumpToTargetPosition(payload.topCursor); } } catch (e) { - dispatch({ type: Action.ErrorOnNewEntries }); + dispatchIfMounted({ type: Action.ErrorOnNewEntries }); } }; @@ -210,7 +218,7 @@ const useFetchEntriesEffect = ( return; } - dispatch({ type: Action.FetchingMoreEntries }); + dispatchIfMounted({ type: Action.FetchingMoreEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -232,14 +240,14 @@ const useFetchEntriesEffect = ( const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ + dispatchIfMounted({ type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, payload, }); return payload.bottomCursor; } catch (e) { - dispatch({ type: Action.ErrorOnMoreEntries }); + dispatchIfMounted({ type: Action.ErrorOnMoreEntries }); } }; @@ -322,7 +330,7 @@ const useFetchEntriesEffect = ( after: props.endTimestamp > prevParams.endTimestamp, }; - dispatch({ type: Action.ExpandRange, payload: shouldExpand }); + dispatchIfMounted({ type: Action.ExpandRange, payload: shouldExpand }); }; const expandRangeEffectDependencies = [ diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts index 9951b62fa64a3..42518127f68bf 100644 --- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts @@ -6,13 +6,15 @@ /* eslint-disable max-classes-per-file */ -import { DependencyList, useEffect, useMemo, useRef, useState } from 'react'; +import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import { useMountedState } from 'react-use'; interface UseTrackedPromiseArgs { createPromise: (...args: Arguments) => Promise; onResolve?: (result: Result) => void; onReject?: (value: unknown) => void; cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never'; + triggerOrThrow?: 'always' | 'whenMounted'; } /** @@ -64,6 +66,16 @@ interface UseTrackedPromiseArgs { * The last argument is a normal React hook dependency list that indicates * under which conditions a new reference to the configuration object should be * used. + * + * The `onResolve`, `onReject` and possible uncatched errors are only triggered + * if the underlying component is mounted. To ensure they always trigger (i.e. + * if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow` + * attribute: + * + * 'whenMounted': (default) they are called only if the component is mounted. + * + * 'always': they always call. The consumer is then responsible of ensuring no + * side effects happen if the underlying component is not mounted. */ export const useTrackedPromise = ( { @@ -71,9 +83,20 @@ export const useTrackedPromise = ( onResolve = noOp, onReject = noOp, cancelPreviousOn = 'never', + triggerOrThrow = 'whenMounted', }: UseTrackedPromiseArgs, dependencies: DependencyList ) => { + const isComponentMounted = useMountedState(); + const shouldTriggerOrThrow = useCallback(() => { + switch (triggerOrThrow) { + case 'always': + return true; + case 'whenMounted': + return isComponentMounted(); + } + }, [isComponentMounted, triggerOrThrow]); + /** * If a promise is currently pending, this holds a reference to it and its * cancellation function. @@ -144,7 +167,7 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onResolve) { + if (onResolve && shouldTriggerOrThrow()) { onResolve(value); } @@ -173,11 +196,13 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onReject) { - onReject(value); - } + if (shouldTriggerOrThrow()) { + if (onReject) { + onReject(value); + } - throw value; + throw value; + } } ), }; From 858befef44d10db1ad388b10e48cad84991c8355 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 9 Nov 2020 13:46:46 +0100 Subject: [PATCH 05/87] [APM] Expose APM event client as part of plugin contract (#82724) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../create_apm_event_client/index.ts | 21 +++++---- .../apm/server/lib/helpers/setup_request.ts | 3 +- x-pack/plugins/apm/server/plugin.ts | 47 +++++++++++++++++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 2bfd3c94ed34c..9020cb1b9953a 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -7,14 +7,16 @@ import { ValuesType } from 'utility-types'; import { APMBaseDoc } from '../../../../../typings/es_schemas/raw/apm_base_doc'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { KibanaRequest } from '../../../../../../../../src/core/server'; +import { + KibanaRequest, + LegacyScopedClusterClient, +} from '../../../../../../../../src/core/server'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { ESSearchRequest, ESSearchResponse, } from '../../../../../typings/elasticsearch'; import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices'; -import { APMRequestHandlerContext } from '../../../../routes/typings'; import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_data'; import { callClientWithDebug } from '../call_client_with_debug'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; @@ -51,20 +53,23 @@ type TypedSearchResponse< export type APMEventClient = ReturnType; export function createApmEventClient({ - context, + esClient, + debug, request, indices, options: { includeFrozen } = { includeFrozen: false }, }: { - context: APMRequestHandlerContext; + esClient: Pick< + LegacyScopedClusterClient, + 'callAsInternalUser' | 'callAsCurrentUser' + >; + debug: boolean; request: KibanaRequest; indices: ApmIndicesConfig; options: { includeFrozen: boolean; }; }) { - const client = context.core.elasticsearch.legacy.client; - return { search( params: TParams, @@ -77,14 +82,14 @@ export function createApmEventClient({ : withProcessorEventFilter; return callClientWithDebug({ - apiCaller: client.callAsCurrentUser, + apiCaller: esClient.callAsCurrentUser, operationName: 'search', params: { ...withPossibleLegacyDataFilter, ignore_throttled: !includeFrozen, }, request, - debug: context.params.query._debug, + debug, }); }, }; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 5e75535c678b3..363c4128137e0 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -88,7 +88,8 @@ export async function setupRequest( const coreSetupRequest = { indices, apmEventClient: createApmEventClient({ - context, + esClient: context.core.elasticsearch.legacy.client, + debug: context.params.query._debug, request, indices, options: { includeFrozen }, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index d3341b6c1b163..44269b1775953 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -10,14 +10,17 @@ import { map, take } from 'rxjs/operators'; import { CoreSetup, CoreStart, + KibanaRequest, Logger, Plugin, PluginInitializerContext, + RequestHandlerContext, } from 'src/core/server'; import { APMConfig, APMXPackConfig, mergeConfigs } from '.'; import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; +import { UI_SETTINGS } from '../../../../src/plugins/data/common'; import { ActionsPlugin } from '../../actions/server'; import { AlertingPlugin } from '../../alerts/server'; import { CloudSetup } from '../../cloud/server'; @@ -30,6 +33,7 @@ import { TaskManagerSetupContract } from '../../task_manager/server'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { registerApmAlerts } from './lib/alerts/register_apm_alerts'; import { createApmTelemetry } from './lib/apm_telemetry'; +import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; @@ -42,6 +46,11 @@ import { uiSettings } from './ui_settings'; export interface APMPluginSetup { config$: Observable; getApmIndices: () => ReturnType; + createApmEventClient: (params: { + debug?: boolean; + request: KibanaRequest; + context: RequestHandlerContext; + }) => Promise>; } export class APMPlugin implements Plugin { @@ -141,13 +150,41 @@ export class APMPlugin implements Plugin { }, }); + const boundGetApmIndices = async () => + getApmIndices({ + savedObjectsClient: await getInternalSavedObjectsClient(core), + config: await mergedConfig$.pipe(take(1)).toPromise(), + }); + return { config$: mergedConfig$, - getApmIndices: async () => - getApmIndices({ - savedObjectsClient: await getInternalSavedObjectsClient(core), - config: await mergedConfig$.pipe(take(1)).toPromise(), - }), + getApmIndices: boundGetApmIndices, + createApmEventClient: async ({ + request, + context, + debug, + }: { + debug?: boolean; + request: KibanaRequest; + context: RequestHandlerContext; + }) => { + const [indices, includeFrozen] = await Promise.all([ + boundGetApmIndices(), + context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN), + ]); + + const esClient = context.core.elasticsearch.legacy.client; + + return createApmEventClient({ + debug: debug ?? false, + esClient, + request, + indices, + options: { + includeFrozen, + }, + }); + }, }; } From 3c525d7341ebe683e6ed8827927c5b0c18e97631 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 12:56:56 +0000 Subject: [PATCH 06/87] [Alerting] adds an Run When field in the alert flyout to assign the action to an Action Group (#82472) Adds a `RunsWhen` field to actions in the Alerts Flyout when creating / editing an Alert which allows the user to assign specific actions to a certain Action Groups --- .../public/alert_types/astros.tsx | 6 +- .../server/alert_types/always_firing.ts | 18 +- x-pack/plugins/triggers_actions_ui/README.md | 21 +- .../lib/check_action_type_enabled.scss | 12 +- .../action_form.test.tsx | 89 ++- .../action_connector_form/action_form.tsx | 637 +++++------------- .../action_type_form.tsx | 339 ++++++++++ .../connector_add_inline.tsx | 153 +++++ .../connector_add_modal.test.tsx | 3 +- .../connector_add_modal.tsx | 13 +- .../sections/alert_form/alert_form.tsx | 51 +- .../alert_create_flyout.ts | 57 ++ .../fixtures/plugins/alerts/server/plugin.ts | 1 + 13 files changed, 846 insertions(+), 554 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 73c7dfea1263b..54f989b93e22f 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -127,9 +127,9 @@ export const PeopleinSpaceExpression: React.FunctionComponent - errs.map((e) => ( -

+ Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) => + errs.map((e, index) => ( +

{field}:`: ${errs}`

)) diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index bb1cb0d97689b..d02406a23045e 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -5,25 +5,31 @@ */ import uuid from 'uuid'; -import { range } from 'lodash'; +import { range, random } from 'lodash'; import { AlertType } from '../../../../plugins/alerts/server'; import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; +const ACTION_GROUPS = [ + { id: 'small', name: 'small' }, + { id: 'medium', name: 'medium' }, + { id: 'large', name: 'large' }, +]; + export const alertType: AlertType = { id: 'example.always-firing', name: 'Always firing', - actionGroups: [{ id: 'default', name: 'default' }], - defaultActionGroupId: 'default', + actionGroups: ACTION_GROUPS, + defaultActionGroupId: 'small', async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) { const count = (state.count ?? 0) + 1; range(instances) - .map(() => ({ id: uuid.v4() })) - .forEach((instance: { id: string }) => { + .map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! })) + .forEach((instance: { id: string; tshirtSize: string }) => { services .alertInstanceFactory(instance.id) .replaceState({ triggerdOnCycle: count }) - .scheduleActions('default'); + .scheduleActions(instance.tshirtSize); }); return { diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index aabb9899cb343..32e157255c0cc 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -1319,19 +1319,19 @@ ActionForm Props definition: interface ActionAccordionFormProps { actions: AlertAction[]; defaultActionGroupId: string; + actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; + setActionGroupIdByIndex?: (group: string, index: number) => void; setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; - actionTypeRegistry: TypeRegistry; - toastNotifications: Pick< - ToastsApi, - 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' - >; + actionTypeRegistry: ActionTypeRegistryContract; + toastNotifications: ToastsSetup; + docLinks: DocLinksStart; actionTypes?: ActionType[]; messageVariables?: ActionVariable[]; defaultActionMessage?: string; - consumer: string; + capabilities: ApplicationStart['capabilities']; } ``` @@ -1339,17 +1339,20 @@ interface ActionAccordionFormProps { |Property|Description| |---|---| |actions|List of actions comes from alert.actions property.| -|defaultActionGroupId|Default action group id to which each new action will belong to.| +|defaultActionGroupId|Default action group id to which each new action will belong by default.| +|actionGroups|Optional. List of action groups to which new action can be assigned. The RunWhen field is only displayed when these action groups are specified| |setActionIdByIndex|Function for changing action 'id' by the proper index in alert.actions array.| +|setActionGroupIdByIndex|Function for changing action 'group' by the proper index in alert.actions array.| |setAlertProperty|Function for changing alert property 'actions'. Used when deleting action from the array to reset it.| |setActionParamsProperty|Function for changing action key/value property by index in alert.actions array.| |http|HttpSetup needed for executing API calls.| |actionTypeRegistry|Registry for action types.| -|toastNotifications|Toast messages.| +|toastNotifications|Toast messages Plugin Setup Contract.| +|docLinks|Documentation links Plugin Start Contract.| |actionTypes|Optional property, which allowes to define a list of available actions specific for a current plugin.| |actionTypes|Optional property, which allowes to define a list of variables for action 'message' property.| |defaultActionMessage|Optional property, which allowes to define a message value for action with 'message' property.| -|consumer|Name of the plugin that creates an action.| +|capabilities|Kibana core's Capabilities ApplicationStart['capabilities'].| AlertsContextProvider value options: diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss index 24dbb865742d8..bb622829e997a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.scss @@ -3,9 +3,15 @@ } .actAccordionActionForm { - .euiCard { - box-shadow: none; - } + background-color: $euiColorLightestShade; +} + +.actAccordionActionForm .euiCard { + box-shadow: none; +} + +.actAccordionActionForm__button { + padding: $euiSizeM; } .actConnectorsListGrid { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 7c718e8248e41..94452e70e6bfa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -6,7 +6,6 @@ import React, { Fragment, lazy } from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, Alert, AlertAction } from '../../../types'; @@ -112,8 +111,6 @@ describe('action_form', () => { }; describe('action_form in alert', () => { - let wrapper: ReactWrapper; - async function setup(customActions?: AlertAction[]) { const { loadAllActions } = jest.requireMock('../../lib/action_connector_api'); loadAllActions.mockResolvedValueOnce([ @@ -217,7 +214,7 @@ describe('action_form', () => { mutedInstanceIds: [], } as unknown) as Alert; - wrapper = mountWithIntl( + const wrapper = mountWithIntl( { setActionIdByIndex={(id: string, index: number) => { initialAlert.actions[index].id = id; }} + actionGroups={[{ id: 'default', name: 'Default' }]} + setActionGroupIdByIndex={(group: string, index: number) => { + initialAlert.actions[index].group = group; + }} setAlertProperty={(_updatedActions: AlertAction[]) => {}} setActionParamsProperty={(key: string, value: any, index: number) => (initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value }) @@ -297,10 +298,12 @@ describe('action_form', () => { await nextTick(); wrapper.update(); }); + + return wrapper; } it('renders available action cards', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); @@ -314,7 +317,7 @@ describe('action_form', () => { }); it('does not render action types disabled by config', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-config-ActionTypeSelectOption"]' ); @@ -322,52 +325,72 @@ describe('action_form', () => { }); it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); expect(actionOption.exists()).toBeTruthy(); }); + it('renders available action groups for the selected action type', async () => { + const wrapper = await setup(); + const actionOption = wrapper.find( + `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` + ); + actionOption.first().simulate('click'); + const actionGroupsSelect = wrapper.find( + `[data-test-subj="addNewActionConnectorActionGroup-0"]` + ); + expect((actionGroupsSelect.first().props() as any).options).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "addNewActionConnectorActionGroup-0-option-default", + "inputDisplay": "Default", + "value": "default", + }, + ] + `); + }); + it('renders available connectors for the selected action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); actionOption.first().simulate('click'); const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}"]`); expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` - Array [ - Object { - "id": "test", - "key": "test", - "label": "Test connector ", - }, - Object { - "id": "test2", - "key": "test2", - "label": "Test connector 2 (preconfigured)", - }, - ] - `); + Array [ + Object { + "id": "test", + "key": "test", + "label": "Test connector ", + }, + Object { + "id": "test2", + "key": "test2", + "label": "Test connector 2 (preconfigured)", + }, + ] + `); }); it('renders only preconfigured connectors for the selected preconfigured action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); actionOption.first().simulate('click'); const combobox = wrapper.find('[data-test-subj="selectActionConnector-preconfigured"]'); expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` - Array [ - Object { - "id": "test3", - "key": "test3", - "label": "Preconfigured Only (preconfigured)", - }, - ] - `); + Array [ + Object { + "id": "test3", + "key": "test3", + "label": "Preconfigured Only (preconfigured)", + }, + ] + `); }); it('does not render "Add connector" button for preconfigured only action type', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); actionOption.first().simulate('click'); const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]'); @@ -378,7 +401,7 @@ describe('action_form', () => { }); it('renders action types disabled by license', async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( '[data-test-subj="disabled-by-license-ActionTypeSelectOption"]' ); @@ -391,7 +414,7 @@ describe('action_form', () => { }); it(`shouldn't render action types without params component`, async () => { - await setup(); + const wrapper = await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionTypeWithoutParams.id}-ActionTypeSelectOption"]` ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 74432157f5659..3a7341afe3e07 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Suspense, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -14,25 +14,13 @@ import { EuiIcon, EuiTitle, EuiSpacer, - EuiFormRow, - EuiComboBox, EuiKeyPadMenuItem, - EuiAccordion, - EuiButtonIcon, - EuiEmptyPrompt, - EuiButtonEmpty, EuiToolTip, - EuiIconTip, EuiLink, - EuiCallOut, - EuiHorizontalRule, - EuiText, - EuiLoadingSpinner, } from '@elastic/eui'; import { HttpSetup, ToastsSetup, ApplicationStart, DocLinksStart } from 'kibana/public'; import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; import { - IErrorObject, ActionTypeModel, ActionTypeRegistryContract, AlertAction, @@ -43,15 +31,19 @@ import { } from '../../../types'; import { SectionLoading } from '../../components/section_loading'; import { ConnectorAddModal } from './connector_add_modal'; +import { ActionTypeForm, ActionTypeFormProps } from './action_type_form'; +import { AddConnectorInline } from './connector_add_inline'; import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants'; -import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionGroup } from '../../../../../alerts/common'; -interface ActionAccordionFormProps { +export interface ActionAccordionFormProps { actions: AlertAction[]; defaultActionGroupId: string; + actionGroups?: ActionGroup[]; setActionIdByIndex: (id: string, index: number) => void; + setActionGroupIdByIndex?: (group: string, index: number) => void; setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; @@ -74,7 +66,9 @@ interface ActiveActionConnectorState { export const ActionForm = ({ actions, defaultActionGroupId, + actionGroups, setActionIdByIndex, + setActionGroupIdByIndex, setAlertProperty, setActionParamsProperty, http, @@ -88,8 +82,6 @@ export const ActionForm = ({ capabilities, docLinks, }: ActionAccordionFormProps) => { - const canSave = hasSaveActionsCapability(capabilities); - const [addModalVisible, setAddModalVisibility] = useState(false); const [activeActionItem, setActiveActionItem] = useState( undefined @@ -101,6 +93,10 @@ export const ActionForm = ({ const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [emptyActionsIds, setEmptyActionsIds] = useState([]); + const closeAddConnectorModal = useCallback(() => setAddModalVisibility(false), [ + setAddModalVisibility, + ]); + // load action types useEffect(() => { (async () => { @@ -183,359 +179,6 @@ export const ActionForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actions, connectors]); - const preconfiguredMessage = i18n.translate( - 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', - { - defaultMessage: '(preconfigured)', - } - ); - - const getSelectedOptions = (actionItemId: string) => { - const selectedConnector = connectors.find((connector) => connector.id === actionItemId); - if ( - !selectedConnector || - // if selected connector is not preconfigured and action type is for preconfiguration only, - // do not show regular connectors of this type - (actionTypesIndex && - !actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig && - !selectedConnector.isPreconfigured) - ) { - return []; - } - const optionTitle = `${selectedConnector.name} ${ - selectedConnector.isPreconfigured ? preconfiguredMessage : '' - }`; - return [ - { - label: optionTitle, - value: optionTitle, - id: actionItemId, - 'data-test-subj': 'itemActionConnector', - }, - ]; - }; - - const getActionTypeForm = ( - actionItem: AlertAction, - actionConnector: ActionConnector, - actionParamsErrors: { - errors: IErrorObject; - }, - index: number - ) => { - if (!actionTypesIndex) { - return null; - } - - const actionType = actionTypesIndex[actionItem.actionTypeId]; - - const optionsList = connectors - .filter( - (connectorItem) => - connectorItem.actionTypeId === actionItem.actionTypeId && - // include only enabled by config connectors or preconfigured - (actionType.enabledInConfig || connectorItem.isPreconfigured) - ) - .map(({ name, id, isPreconfigured }) => ({ - label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`, - key: id, - id, - })); - const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); - if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; - const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; - const checkEnabledResult = checkActionFormActionTypeEnabled( - actionTypesIndex[actionConnector.actionTypeId], - connectors.filter((connector) => connector.isPreconfigured) - ); - - const accordionContent = checkEnabledResult.isEnabled ? ( - - - - - } - labelAppend={ - canSave && - actionTypesIndex && - actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - - ) : null - } - > - { - setActionIdByIndex(selectedOptions[0].id ?? '', index); - }} - isClearable={false} - /> - - - - - {ParamsFieldsComponent ? ( - - - - - - } - > - - - ) : null} - - ) : ( - checkEnabledResult.messageCard - ); - - return ( - - - - - - - -
- - - - - - {checkEnabledResult.isEnabled === false && ( - - - - )} - - -
-
-
- - } - extraAction={ - { - const updatedActions = actions.filter( - (_item: AlertAction, i: number) => i !== index - ); - setAlertProperty(updatedActions); - setIsAddActionPanelOpen( - updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === - 0 - ); - setActiveActionItem(undefined); - }} - /> - } - paddingSize="l" - > - {accordionContent} -
- -
- ); - }; - - const getAddConnectorsForm = (actionItem: AlertAction, index: number) => { - const actionTypeName = actionTypesIndex - ? actionTypesIndex[actionItem.actionTypeId].name - : actionItem.actionTypeId; - const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); - if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; - - const noConnectorsLabel = ( - - ); - return ( - - - - - - - -
- -
-
-
- - } - extraAction={ - { - const updatedActions = actions.filter( - (_item: AlertAction, i: number) => i !== index - ); - setAlertProperty(updatedActions); - setIsAddActionPanelOpen( - updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === - 0 - ); - setActiveActionItem(undefined); - }} - /> - } - paddingSize="l" - > - {canSave ? ( - actionItem.id === emptyId) ? ( - noConnectorsLabel - ) : ( - - ) - } - actions={[ - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - , - ]} - /> - ) : ( - -

- -

-
- )} -
- -
- ); - }; - function addActionType(actionTypeModel: ActionTypeModel) { if (!defaultActionGroupId) { toastNotifications!.addDanger({ @@ -628,116 +271,172 @@ export const ActionForm = ({ }); } - const alertActionsList = actions.map((actionItem: AlertAction, index: number) => { - const actionConnector = connectors.find((field) => field.id === actionItem.id); - // connectors doesn't exists - if (!actionConnector) { - return getAddConnectorsForm(actionItem, index); - } - - const actionErrors: { errors: IErrorObject } = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); - - return getActionTypeForm(actionItem, actionConnector, actionErrors, index); - }); - - return ( + return isLoadingConnectors ? ( + + + + ) : ( - {isLoadingConnectors ? ( - + +

- - ) : ( - - -

- + + + {actionTypesIndex && + actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find((field) => field.id === actionItem.id); + // connectors doesn't exists + if (!actionConnector) { + return ( + { + const updatedActions = actions.filter( + (_item: AlertAction, i: number) => i !== index + ); + setAlertProperty(updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id) + .length === 0 + ); + setActiveActionItem(undefined); + }} + onAddConnector={() => { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} /> -

-
- - {alertActionsList} - {isAddActionPanelOpen === false ? ( -
- - - - setIsAddActionPanelOpen(true)} - > - - - - -
- ) : null} - {isAddActionPanelOpen ? ( - - - - -
+ ); + } + + const actionParamsErrors: ActionTypeFormProps['actionParamsErrors'] = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + + return ( + { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} + onConnectorSelected={(id: string) => { + setActionIdByIndex(id, index); + }} + onDeleteAction={() => { + const updatedActions = actions.filter( + (_item: AlertAction, i: number) => i !== index + ); + setAlertProperty(updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === + 0 + ); + setActiveActionItem(undefined); + }} + /> + ); + })} + + {isAddActionPanelOpen ? ( + + + + +
+ +
+
+
+ {hasDisabledByLicenseActionTypes && ( + + +
+ -
-
-
- {hasDisabledByLicenseActionTypes && ( - - -
- - - -
-
-
- )} -
- - - {isLoadingActionTypes ? ( - - - - ) : ( - actionTypeNodes - )} - -
- ) : null} + +
+
+
+ )} +
+ + + {isLoadingActionTypes ? ( + + + + ) : ( + actionTypeNodes + )} +
+ ) : ( + + + setIsAddActionPanelOpen(true)} + > + + + + )} - {actionTypesIndex && activeActionItem ? ( + {actionTypesIndex && activeActionItem && addModalVisible ? ( { connectors.push(savedAction); setActionIdByIndex(savedAction.id, activeActionItem.index); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx new file mode 100644 index 0000000000000..38468283b9c19 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -0,0 +1,339 @@ +/* + * 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 React, { Fragment, Suspense, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiFormRow, + EuiComboBox, + EuiAccordion, + EuiButtonIcon, + EuiButtonEmpty, + EuiIconTip, + EuiText, + EuiFormLabel, + EuiFormControlLayout, + EuiSuperSelect, + EuiLoadingSpinner, + EuiBadge, +} from '@elastic/eui'; +import { IErrorObject, AlertAction, ActionTypeIndex, ActionConnector } from '../../../types'; +import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionAccordionFormProps } from './action_form'; + +export type ActionTypeFormProps = { + actionItem: AlertAction; + actionConnector: ActionConnector; + actionParamsErrors: { + errors: IErrorObject; + }; + index: number; + onAddConnector: () => void; + onConnectorSelected: (id: string) => void; + onDeleteAction: () => void; + setActionParamsProperty: (key: string, value: any, index: number) => void; + actionTypesIndex: ActionTypeIndex; + connectors: ActionConnector[]; +} & Pick< + ActionAccordionFormProps, + | 'defaultActionGroupId' + | 'actionGroups' + | 'setActionGroupIdByIndex' + | 'setActionParamsProperty' + | 'http' + | 'actionTypeRegistry' + | 'toastNotifications' + | 'docLinks' + | 'messageVariables' + | 'defaultActionMessage' + | 'capabilities' +>; + +const preconfiguredMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', + { + defaultMessage: '(preconfigured)', + } +); + +export const ActionTypeForm = ({ + actionItem, + actionConnector, + actionParamsErrors, + index, + onAddConnector, + onConnectorSelected, + onDeleteAction, + setActionParamsProperty, + actionTypesIndex, + connectors, + http, + toastNotifications, + docLinks, + capabilities, + actionTypeRegistry, + defaultActionGroupId, + defaultActionMessage, + messageVariables, + actionGroups, + setActionGroupIdByIndex, +}: ActionTypeFormProps) => { + const [isOpen, setIsOpen] = useState(true); + + const canSave = hasSaveActionsCapability(capabilities); + const getSelectedOptions = (actionItemId: string) => { + const selectedConnector = connectors.find((connector) => connector.id === actionItemId); + if ( + !selectedConnector || + // if selected connector is not preconfigured and action type is for preconfiguration only, + // do not show regular connectors of this type + (actionTypesIndex && + !actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig && + !selectedConnector.isPreconfigured) + ) { + return []; + } + const optionTitle = `${selectedConnector.name} ${ + selectedConnector.isPreconfigured ? preconfiguredMessage : '' + }`; + return [ + { + label: optionTitle, + value: optionTitle, + id: actionItemId, + 'data-test-subj': 'itemActionConnector', + }, + ]; + }; + + const actionType = actionTypesIndex[actionItem.actionTypeId]; + + const optionsList = connectors + .filter( + (connectorItem) => + connectorItem.actionTypeId === actionItem.actionTypeId && + // include only enabled by config connectors or preconfigured + (actionType.enabledInConfig || connectorItem.isPreconfigured) + ) + .map(({ name, id, isPreconfigured }) => ({ + label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`, + key: id, + id, + })); + const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); + if (!actionTypeRegistered) return null; + + const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionTypesIndex[actionConnector.actionTypeId], + connectors.filter((connector) => connector.isPreconfigured) + ); + + const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId); + const selectedActionGroup = + actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup; + + const accordionContent = checkEnabledResult.isEnabled ? ( + + {actionGroups && selectedActionGroup && setActionGroupIdByIndex && ( + + + + + + + } + > + ({ + value, + inputDisplay: name, + 'data-test-subj': `addNewActionConnectorActionGroup-${index}-option-${value}`, + }))} + valueOfSelected={selectedActionGroup.id} + onChange={(group) => { + setActionGroupIdByIndex(group, index); + }} + /> + + + + + + )} + + + + } + labelAppend={ + canSave && + actionTypesIndex && + actionTypesIndex[actionConnector.actionTypeId].enabledInConfig ? ( + + + + ) : null + } + > + { + onConnectorSelected(selectedOptions[0].id ?? ''); + }} + isClearable={false} + /> + + + + + {ParamsFieldsComponent ? ( + + + + + + } + > + + + ) : null} + + ) : ( + checkEnabledResult.messageCard + ); + + return ( + + + + + + + +
+ + + + + {selectedActionGroup && !isOpen && ( + + {selectedActionGroup.name} + + )} + + {checkEnabledResult.isEnabled === false && ( + + + + )} + + +
+
+
+ + } + extraAction={ + + } + > + {accordionContent} +
+ +
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx new file mode 100644 index 0000000000000..97baf4a36cb4c --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -0,0 +1,153 @@ +/* + * 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 React, { Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiAccordion, + EuiButtonIcon, + EuiEmptyPrompt, + EuiCallOut, + EuiText, +} from '@elastic/eui'; +import { AlertAction, ActionTypeIndex } from '../../../types'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { ActionAccordionFormProps } from './action_form'; + +type AddConnectorInFormProps = { + actionTypesIndex: ActionTypeIndex; + actionItem: AlertAction; + index: number; + onAddConnector: () => void; + onDeleteConnector: () => void; + emptyActionsIds: string[]; +} & Pick; + +export const AddConnectorInline = ({ + actionTypesIndex, + actionItem, + index, + onAddConnector, + onDeleteConnector, + actionTypeRegistry, + emptyActionsIds, + defaultActionGroupId, + capabilities, +}: AddConnectorInFormProps) => { + const canSave = hasSaveActionsCapability(capabilities); + + const actionTypeName = actionTypesIndex + ? actionTypesIndex[actionItem.actionTypeId].name + : actionItem.actionTypeId; + const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); + if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; + + const noConnectorsLabel = ( + + ); + return ( + + + + + + + +
+ +
+
+
+ + } + extraAction={ + + } + paddingSize="l" + > + {canSave ? ( + actionItem.id === emptyId) ? ( + noConnectorsLabel + ) : ( + + ) + } + actions={[ + + + , + ]} + /> + ) : ( + +

+ +

+
+ )} +
+ +
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index cba9eea3cf3f7..71a3936ed5055 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -65,8 +65,7 @@ describe('connector_add_modal', () => { const wrapper = mountWithIntl( {}} + onClose={() => {}} actionType={actionType} http={deps!.http} actionTypeRegistry={deps!.actionTypeRegistry} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 13ec8395aa557..de27256bf566c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -32,8 +32,7 @@ import { interface ConnectorAddModalProps { actionType: ActionType; - addModalVisible: boolean; - setAddModalVisibility: React.Dispatch>; + onClose: () => void; postSaveEventHandler?: (savedAction: ActionConnector) => void; http: HttpSetup; actionTypeRegistry: ActionTypeRegistryContract; @@ -48,8 +47,7 @@ interface ConnectorAddModalProps { export const ConnectorAddModal = ({ actionType, - addModalVisible, - setAddModalVisibility, + onClose, postSaveEventHandler, http, toastNotifications, @@ -79,14 +77,11 @@ export const ConnectorAddModal = ({ >(undefined); const closeModal = useCallback(() => { - setAddModalVisibility(false); setConnector(initialConnector); setServerError(undefined); - }, [initialConnector, setAddModalVisibility]); + onClose(); + }, [initialConnector, onClose]); - if (!addModalVisible) { - return null; - } const actionTypeModel = actionTypeRegistry.get(actionType.id); const errors = { ...actionTypeModel?.validateConnector(connector).errors, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 9a637ea750f81..20ad9a8d7c701 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useState, useEffect, Suspense } from 'react'; +import React, { Fragment, useState, useEffect, Suspense, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -153,9 +153,17 @@ export const AlertForm = ({ setAlertTypeModel(alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null); }, [alert, alertTypeRegistry]); - const setAlertProperty = (key: string, value: any) => { - dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); - }; + const setAlertProperty = useCallback( + (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }, + [dispatch] + ); + + const setActions = useCallback( + (updatedActions: AlertAction[]) => setAlertProperty('actions', updatedActions), + [setAlertProperty] + ); const setAlertParams = (key: string, value: any) => { dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } }); @@ -169,9 +177,12 @@ export const AlertForm = ({ dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } }); }; - const setActionParamsProperty = (key: string, value: any, index: number) => { - dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); - }; + const setActionParamsProperty = useCallback( + (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); + }, + [dispatch] + ); const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; @@ -202,6 +213,7 @@ export const AlertForm = ({ label={item.name} onClick={() => { setAlertProperty('alertTypeId', item.id); + setActions([]); setAlertTypeModel(item); setAlertProperty('params', {}); if (alertTypesIndex && alertTypesIndex.has(item.id)) { @@ -289,26 +301,25 @@ export const AlertForm = ({ /> ) : null} - {canShowActions && defaultActionGroupId ? ( + {canShowActions && + defaultActionGroupId && + alertTypeModel && + alertTypesIndex?.has(alert.alertTypeId) ? ( - a.name.toUpperCase().localeCompare(b.name.toUpperCase()) - ) - : undefined - } + messageVariables={actionVariablesFromAlertType( + alertTypesIndex.get(alert.alertTypeId)! + ).sort((a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase()))} defaultActionGroupId={defaultActionGroupId} + actionGroups={alertTypesIndex.get(alert.alertTypeId)!.actionGroups} setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)} - setAlertProperty={(updatedActions: AlertAction[]) => - setAlertProperty('actions', updatedActions) - } - setActionParamsProperty={(key: string, value: any, index: number) => - setActionParamsProperty(key, value, index) + setActionGroupIdByIndex={(group: string, index: number) => + setActionProperty('group', group, index) } + setAlertProperty={setActions} + setActionParamsProperty={setActionParamsProperty} http={http} actionTypeRegistry={actionTypeRegistry} defaultActionMessage={alertTypeModel?.defaultActionMessage} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 7d99d3635106d..ee0de582a9bff 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -55,6 +55,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.click(); } + async function defineAlwaysFiringAlert(alertName: string) { + await pageObjects.triggersActionsUI.clickCreateAlertButton(); + await testSubjects.setValue('alertNameInput', alertName); + await testSubjects.click('test.always-firing-SelectOption'); + } + describe('create alert', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); @@ -106,6 +112,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); }); + it('should create an alert with actions in multiple groups', async () => { + const alertName = generateUniqueKey(); + await defineAlwaysFiringAlert(alertName); + + // create Slack connector and attach an action using it + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.click('addNewActionConnectorButton-.slack'); + const slackConnectorName = generateUniqueKey(); + await testSubjects.setValue('nameInput', slackConnectorName); + await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const createdConnectorToastTitle = await pageObjects.common.closeToast(); + expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + await testSubjects.setValue('messageTextArea', 'test message '); + await ( + await find.byCssSelector( + '[data-test-subj="alertActionAccordion-0"] [data-test-subj="messageTextArea"]' + ) + ).type('some text '); + + await testSubjects.click('addAlertActionButton'); + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.setValue('messageTextArea', 'test message '); + await ( + await find.byCssSelector( + '[data-test-subj="alertActionAccordion-1"] [data-test-subj="messageTextArea"]' + ) + ).type('some text '); + + await testSubjects.click('addNewActionConnectorActionGroup-1'); + await testSubjects.click('addNewActionConnectorActionGroup-1-option-other'); + + await testSubjects.click('saveAlertButton'); + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created alert "${alertName}"`); + await pageObjects.triggersActionsUI.searchAlerts(alertName); + const searchResultsAfterSave = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResultsAfterSave).to.eql([ + { + name: alertName, + tagsText: '', + alertType: 'Always Firing', + interval: '1m', + }, + ]); + + // clean up created alert + const alertsToDelete = await getAlertsByName(alertName); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); + }); + it('should show save confirmation before creating alert with no actions', async () => { const alertName = generateUniqueKey(); await defineAlert(alertName); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index e3927f6bfffb9..6f9d010378624 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -78,6 +78,7 @@ function createAlwaysFiringAlertType(alerts: AlertingSetup) { { id: 'default', name: 'Default' }, { id: 'other', name: 'Other' }, ], + defaultActionGroupId: 'default', producer: 'alerts', async executor(alertExecutorOptions: any) { const { services, state, params } = alertExecutorOptions; From d1ef0d6704237cade5ff6a4246e42148dadb0b9e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 9 Nov 2020 13:11:51 +0000 Subject: [PATCH 07/87] skip flaky suite (#57426) --- .../functional_with_es_ssl/apps/triggers_actions_ui/details.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 9e4006681dc8d..1d86d95b7a796 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -306,7 +306,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('Alert Instances', function () { + // FLAKY: https://github.com/elastic/kibana/issues/57426 + describe.skip('Alert Instances', function () { const testRunUuid = uuid.v4(); let alert: any; From f2f76e104af5d0515773c173475efda7d80c1d31 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 9 Nov 2020 14:29:53 +0100 Subject: [PATCH 08/87] [ILM] Migrate Delete phase and name field to Form Lib (#82834) * remove use of legacy state system and legacy serialization * remove legacy min_age input component and re-add missing import * rename shared -> shared_fields for more clarity * some more cleanup and fixing regressions on policy name for creating new policy from existing policy * move extract policy static code to lib folder and remove "policies" dir from services * fix jest tests and minor policy flyout inconsistency * remove legacy helper * fix client integration tests * fix min for set index priority * moved save policy function into edit policy section * remove unused translations * refactor form files to own edit_policy/form folder * remove "fix errors" badge to fix UX - users can see errors in line before pressing save so the value of this badge has diminished * fix i18n after removing phase error badge Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../edit_policy/edit_policy.helpers.tsx | 5 + .../edit_policy/edit_policy.test.ts | 3 +- .../__jest__/components/edit_policy.test.tsx | 242 ++++---- .../public/application/lib/policies.ts | 32 ++ .../sections/edit_policy/components/index.ts | 3 - .../components/min_age_input_legacy.tsx | 263 --------- .../components/phase_error_message.tsx | 19 - .../phases/cold_phase/cold_phase.tsx | 23 +- .../{ => delete_phase}/delete_phase.tsx | 94 +-- .../components/phases/delete_phase/index.ts | 7 + .../components/phases/hot_phase/hot_phase.tsx | 12 +- .../components/cloud_data_tier_callout.tsx | 0 .../components/data_tier_allocation.scss | 0 .../components/data_tier_allocation.tsx | 0 .../components/default_allocation_notice.tsx | 0 .../components/index.ts | 0 .../components/no_node_attributes_warning.tsx | 0 .../components/node_allocation.tsx | 9 +- .../components/node_attrs_details.tsx | 0 .../components/node_data_provider.tsx | 0 .../components/types.ts | 0 .../data_tier_allocation_field.tsx | 0 .../data_tier_allocation_field/index.ts | 0 .../forcemerge_field.tsx | 6 +- .../phases/{shared => shared_fields}/index.ts | 2 + .../min_age_input_field/index.ts | 0 .../min_age_input_field.tsx | 0 .../min_age_input_field/util.ts | 0 .../set_priority_input.tsx | 9 +- .../snapshot_policies_field.tsx} | 113 ++-- .../phases/warm_phase/warm_phase.tsx | 27 +- .../components/policy_json_flyout.tsx | 22 +- .../edit_policy/edit_policy.container.tsx | 23 +- .../sections/edit_policy/edit_policy.tsx | 537 ++++++++---------- .../edit_policy/edit_policy_context.tsx | 12 +- .../edit_policy/{ => form}/deserializer.ts | 21 +- .../sections/edit_policy/form/index.ts | 13 + .../{form_schema.ts => form/schema.ts} | 53 +- .../edit_policy/{ => form}/serializer.ts | 24 +- .../validations.ts} | 77 ++- .../sections/edit_policy/i18n_texts.ts | 36 ++ .../edit_policy/save_policy.ts} | 15 +- .../application/sections/edit_policy/types.ts | 5 + .../services/policies/delete_phase.ts | 88 --- .../policies/policy_serialization.test.ts | 198 ------- .../services/policies/policy_serialization.ts | 82 --- .../services/policies/policy_validation.ts | 144 ----- .../public/shared_imports.ts | 3 + .../translations/translations/ja-JP.json | 6 - .../translations/translations/zh-CN.json | 6 - 50 files changed, 749 insertions(+), 1485 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{ => delete_phase}/delete_phase.tsx (50%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/cloud_data_tier_callout.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/data_tier_allocation.scss (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/data_tier_allocation.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/default_allocation_notice.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/no_node_attributes_warning.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_allocation.tsx (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_attrs_details.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/node_data_provider.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/components/types.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/data_tier_allocation_field.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/data_tier_allocation_field/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/forcemerge_field.tsx (94%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/index.ts (88%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/index.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/min_age_input_field.tsx (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/min_age_input_field/util.ts (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{shared => shared_fields}/set_priority_input.tsx (83%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{snapshot_policies.tsx => phases/shared_fields/snapshot_policies_field.tsx} (68%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => form}/deserializer.ts (82%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{form_schema.ts => form/schema.ts} (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => form}/serializer.ts (90%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{form_validations.ts => form/validations.ts} (50%) rename x-pack/plugins/index_lifecycle_management/public/application/{services/policies/policy_save.ts => sections/edit_policy/save_policy.ts} (84%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 0b9f47e188d15..646978dd68153 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -221,6 +221,11 @@ export const setup = async () => { setFreeze, setIndexPriority: setIndexPriority('cold'), }, + delete: { + enable: enable('delete'), + setMinAgeValue: setMinAgeValue('delete'), + setMinAgeUnits: setMinAgeUnits('delete'), + }, }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 11fadf51f27f8..4ee67d1ed8a19 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -367,7 +367,6 @@ describe('', () => { expect(testBed.find('snapshotPolicyCombobox').prop('data-currentvalue')).toEqual([ { label: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy, - value: DELETE_PHASE_POLICY.policy.phases.delete?.actions.wait_for_snapshot?.policy, }, ]); }); @@ -412,7 +411,7 @@ describe('', () => { test('wait for snapshot field should delete action if field is empty', async () => { const { actions } = testBed; - actions.setWaitForSnapshotPolicy(''); + await actions.setWaitForSnapshotPolicy(''); await actions.savePolicy(); const expected = { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index 4a3fedfb264ac..43910583ceec9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -20,27 +20,27 @@ import { notificationServiceMock, fatalErrorsServiceMock, } from '../../../../../src/core/public/mocks'; + import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks'; + import { CloudSetup } from '../../../cloud/public'; import { EditPolicy } from '../../public/application/sections/edit_policy/edit_policy'; +import { + EditPolicyContextProvider, + EditPolicyContextValue, +} from '../../public/application/sections/edit_policy/edit_policy_context'; + import { KibanaContextProvider } from '../../public/shared_imports'; + import { init as initHttp } from '../../public/application/services/http'; import { init as initUiMetric } from '../../public/application/services/ui_metric'; import { init as initNotification } from '../../public/application/services/notification'; import { PolicyFromES } from '../../common/types'; -import { - positiveNumberRequiredMessage, - policyNameRequiredMessage, - policyNameStartsWithUnderscoreErrorMessage, - policyNameContainsCommaErrorMessage, - policyNameContainsSpaceErrorMessage, - policyNameMustBeDifferentErrorMessage, - policyNameAlreadyUsedErrorMessage, -} from '../../public/application/services/policies/policy_validation'; import { i18nTexts } from '../../public/application/sections/edit_policy/i18n_texts'; import { editPolicyHelpers } from './helpers'; +import { defaultPolicy } from '../../public/application/constants'; // @ts-ignore initHttp(axios.create({ adapter: axiosXhrAdapter })); @@ -122,14 +122,11 @@ const noRollover = async (rendered: ReactWrapper) => { const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { return findTestSubject(rendered, `${phase}-selectedNodeAttrs`); }; -const setPolicyName = (rendered: ReactWrapper, policyName: string) => { +const setPolicyName = async (rendered: ReactWrapper, policyName: string) => { const policyNameField = findTestSubject(rendered, 'policyNameField'); - policyNameField.simulate('change', { target: { value: policyName } }); - rendered.update(); -}; -const setPhaseAfterLegacy = (rendered: ReactWrapper, phase: string, after: string | number) => { - const afterInput = rendered.find(`input#${phase}-selectedMinimumAge`); - afterInput.simulate('change', { target: { value: after } }); + await act(async () => { + policyNameField.simulate('change', { target: { value: policyName } }); + }); rendered.update(); }; const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: string | number) => { @@ -157,6 +154,32 @@ const save = async (rendered: ReactWrapper) => { }); rendered.update(); }; + +const MyComponent = ({ + isCloudEnabled, + isNewPolicy, + policy: _policy, + existingPolicies, + getUrlForApp, + policyName, +}: EditPolicyContextValue & { isCloudEnabled: boolean }) => { + return ( + + + + + + ); +}; + describe('edit policy', () => { beforeAll(() => { jest.useFakeTimers(); @@ -179,14 +202,14 @@ describe('edit policy', () => { beforeEach(() => { component = ( - - - + ); ({ http } = editPolicyHelpers.setup()); @@ -198,62 +221,78 @@ describe('edit policy', () => { test('should show error when trying to save empty form', async () => { const rendered = mountWithIntl(component); await save(rendered); - expectedErrorMessages(rendered, [policyNameRequiredMessage]); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameRequiredMessage]); }); test('should show error when trying to save policy name with space', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'my policy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameContainsSpaceErrorMessage]); + await setPolicyName(rendered, 'my policy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); }); test('should show error when trying to save policy name that is already used', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'testy0'); - rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [policyNameAlreadyUsedErrorMessage]); + await setPolicyName(rendered, 'testy0'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + ]); }); test('should show error when trying to save as new policy but using the same name', async () => { component = ( - ); const rendered = mountWithIntl(component); findTestSubject(rendered, 'saveAsNewSwitch').simulate('click'); rendered.update(); - setPolicyName(rendered, 'testy0'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameMustBeDifferentErrorMessage]); + await setPolicyName(rendered, 'testy0'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + ]); }); test('should show error when trying to save policy name with comma', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'my,policy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameContainsCommaErrorMessage]); + await setPolicyName(rendered, 'my,policy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); }); test('should show error when trying to save policy name starting with underscore', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, '_mypolicy'); - await save(rendered); - expectedErrorMessages(rendered, [policyNameStartsWithUnderscoreErrorMessage]); + await setPolicyName(rendered, '_mypolicy'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [ + i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, + ]); }); test('should show correct json in policy flyout', async () => { - const rendered = mountWithIntl(component); + const rendered = mountWithIntl( + + ); await act(async () => { findTestSubject(rendered, 'requestButton').simulate('click'); }); rendered.update(); + const json = rendered.find(`code`).text(); - const expected = `PUT _ilm/policy/\n${JSON.stringify( + const expected = `PUT _ilm/policy/my-policy\n${JSON.stringify( { policy: { phases: { @@ -282,7 +321,7 @@ describe('edit policy', () => { test('should show errors when trying to save with no max size and no max age', async () => { const rendered = mountWithIntl(component); expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy(); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '' } }); @@ -298,7 +337,7 @@ describe('edit policy', () => { }); test('should show number above 0 required error when trying to save with -1 for max size', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -309,7 +348,7 @@ describe('edit policy', () => { }); test('should show number above 0 required error when trying to save with 0 for max size', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -319,7 +358,7 @@ describe('edit policy', () => { }); test('should show number above 0 required error when trying to save with -1 for max age', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '-1' } }); @@ -329,7 +368,7 @@ describe('edit policy', () => { }); test('should show number above 0 required error when trying to save with 0 for max age', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '0' } }); @@ -337,21 +376,21 @@ describe('edit policy', () => { waitForFormLibValidation(rendered); expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); - test('should show forcemerge input when rollover enabled', () => { + test('should show forcemerge input when rollover enabled', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeTruthy(); }); test('should hide forcemerge input when rollover is disabled', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await noRollover(rendered); waitForFormLibValidation(rendered); expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy(); }); test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); act(() => { findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); }); @@ -365,7 +404,7 @@ describe('edit policy', () => { }); test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); rendered.update(); const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments'); @@ -379,7 +418,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await setPhaseIndexPriority(rendered, 'hot', '-1'); waitForFormLibValidation(rendered); expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); @@ -397,7 +436,7 @@ describe('edit policy', () => { test('should show number required error when trying to save empty warm phase', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', ''); waitForFormLibValidation(rendered); @@ -406,7 +445,7 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '0'); waitForFormLibValidation(rendered); @@ -415,7 +454,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save warm phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '-1'); waitForFormLibValidation(rendered); @@ -424,7 +463,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); await setPhaseAfter(rendered, 'warm', '-1'); @@ -434,7 +473,7 @@ describe('edit policy', () => { test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); act(() => { findTestSubject(rendered, 'shrinkSwitch').simulate('click'); @@ -451,7 +490,7 @@ describe('edit policy', () => { test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); act(() => { @@ -468,7 +507,7 @@ describe('edit policy', () => { test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); act(() => { @@ -485,7 +524,7 @@ describe('edit policy', () => { test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); await setPhaseAfter(rendered, 'warm', '1'); await act(async () => { @@ -503,7 +542,7 @@ describe('edit policy', () => { server.respondImmediately = false; const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); @@ -517,7 +556,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -527,7 +566,7 @@ describe('edit policy', () => { test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -539,7 +578,7 @@ describe('edit policy', () => { test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); @@ -568,7 +607,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); @@ -581,7 +620,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); @@ -594,7 +633,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); @@ -611,7 +650,7 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '0'); waitForFormLibValidation(rendered); @@ -621,7 +660,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save cold phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '-1'); waitForFormLibValidation(rendered); @@ -631,7 +670,7 @@ describe('edit policy', () => { server.respondImmediately = false; const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); @@ -645,7 +684,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -655,7 +694,7 @@ describe('edit policy', () => { test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -667,7 +706,7 @@ describe('edit policy', () => { test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); @@ -689,7 +728,7 @@ describe('edit policy', () => { test('should show positive number required error when trying to save with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); await setPhaseAfter(rendered, 'cold', '1'); await setPhaseIndexPriority(rendered, 'cold', '-1'); @@ -704,7 +743,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); @@ -717,7 +756,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); @@ -730,7 +769,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); @@ -740,20 +779,20 @@ describe('edit policy', () => { test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfterLegacy(rendered, 'delete', '0'); - await save(rendered); + await setPhaseAfter(rendered, 'delete', '0'); + waitForFormLibValidation(rendered); expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save delete phase with -1 for after', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfterLegacy(rendered, 'delete', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'delete', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); }); describe('not on cloud', () => { @@ -768,7 +807,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -782,14 +821,13 @@ describe('edit policy', () => { describe('on cloud', () => { beforeEach(() => { component = ( - - - + ); ({ http } = editPolicyHelpers.setup()); ({ server, httpRequestsMockHelpers } = http); @@ -808,7 +846,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -829,7 +867,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); @@ -849,7 +887,7 @@ describe('edit policy', () => { }); const rendered = mountWithIntl(component); await noRollover(rendered); - setPolicyName(rendered, 'mypolicy'); + await setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'cloudDataTierCallout').exists()).toBeTruthy(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts new file mode 100644 index 0000000000000..c4a91978a3765 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/policies.ts @@ -0,0 +1,32 @@ +/* + * 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 { PolicyFromES } from '../../../common/types'; + +export const splitSizeAndUnits = (field: string): { size: string; units: string } => { + let size = ''; + let units = ''; + + const result = /(\d+)(\w+)/.exec(field); + if (result) { + size = result[1]; + units = result[2]; + } + + return { + size, + units, + }; +}; + +export const getPolicyByName = ( + policies: PolicyFromES[] | null | undefined, + policyName: string = '' +): PolicyFromES | undefined => { + if (policies && policies.length > 0) { + return policies.find((policy: PolicyFromES) => policy.name === policyName); + } +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index a04608338718e..326f6ff87dc3b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -7,11 +7,8 @@ export { ActiveBadge } from './active_badge'; export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; -export { MinAgeInput } from './min_age_input_legacy'; export { OptionalLabel } from './optional_label'; -export { PhaseErrorMessage } from './phase_error_message'; export { PolicyJsonFlyout } from './policy_json_flyout'; -export { SnapshotPolicies } from './snapshot_policies'; export { DescribedFormField } from './described_form_field'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx deleted file mode 100644 index 6fcf35b799289..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx +++ /dev/null @@ -1,263 +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 React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; - -import { LearnMoreLink } from './learn_more_link'; -import { ErrableFormRow } from './form_errors'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -import { PhaseWithMinAge, Phases } from '../../../../../common/types'; - -function getTimingLabelForPhase(phase: keyof Phases) { - // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. - switch (phase) { - case 'warm': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel', { - defaultMessage: 'Timing for warm phase', - }); - - case 'cold': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel', { - defaultMessage: 'Timing for cold phase', - }); - - case 'delete': - return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', { - defaultMessage: 'Timing for delete phase', - }); - } -} - -function getUnitsAriaLabelForPhase(phase: keyof Phases) { - // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. - switch (phase) { - case 'warm': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of warm phase', - } - ); - - case 'cold': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of cold phase', - } - ); - - case 'delete': - return i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel', - { - defaultMessage: 'Units for timing of delete phase', - } - ); - } -} - -interface Props { - rolloverEnabled: boolean; - errors?: PhaseValidationErrors; - phase: keyof Phases & string; - phaseData: T; - setPhaseData: (dataKey: keyof T & string, value: string) => void; - isShowingErrors: boolean; -} - -export const MinAgeInput = ({ - rolloverEnabled, - errors, - phaseData, - phase, - setPhaseData, - isShowingErrors, -}: React.PropsWithChildren>): React.ReactElement => { - let daysOptionLabel; - let hoursOptionLabel; - let minutesOptionLabel; - let secondsOptionLabel; - let millisecondsOptionLabel; - let microsecondsOptionLabel; - let nanosecondsOptionLabel; - - if (rolloverEnabled) { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel', - { - defaultMessage: 'days from rollover', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel', - { - defaultMessage: 'hours from rollover', - } - ); - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel', - { - defaultMessage: 'minutes from rollover', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel', - { - defaultMessage: 'seconds from rollover', - } - ); - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from rollover', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from rollover', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from rollover', - } - ); - } else { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel', - { - defaultMessage: 'days from index creation', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel', - { - defaultMessage: 'hours from index creation', - } - ); - - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel', - { - defaultMessage: 'minutes from index creation', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel', - { - defaultMessage: 'seconds from index creation', - } - ); - - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from index creation', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from index creation', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from index creation', - } - ); - } - - // check that these strings are valid properties - const selectedMinimumAgeProperty = propertyof('selectedMinimumAge'); - const selectedMinimumAgeUnitsProperty = propertyof('selectedMinimumAgeUnits'); - return ( - - - - } - /> - } - > - { - setPhaseData(selectedMinimumAgeProperty, e.target.value); - }} - min={0} - /> - - - - - setPhaseData(selectedMinimumAgeUnitsProperty, e.target.value)} - options={[ - { - value: 'd', - text: daysOptionLabel, - }, - { - value: 'h', - text: hoursOptionLabel, - }, - { - value: 'm', - text: minutesOptionLabel, - }, - { - value: 's', - text: secondsOptionLabel, - }, - { - value: 'ms', - text: millisecondsOptionLabel, - }, - { - value: 'micros', - text: microsecondsOptionLabel, - }, - { - value: 'nanos', - text: nanosecondsOptionLabel, - }, - ]} - /> - - - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx deleted file mode 100644 index 750f68543f221..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx +++ /dev/null @@ -1,19 +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 React from 'react'; -import { EuiBadge } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const PhaseErrorMessage = ({ isShowingErrors }: { isShowingErrors: boolean }) => { - return isShowingErrors ? ( - - - - ) : null; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 84e955a91ad7c..b87243bd1a9a1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -13,19 +13,13 @@ import { EuiDescribedFormGroup, EuiTextColor } from '@elastic/eui'; import { Phases } from '../../../../../../../common/types'; -import { - useFormData, - useFormContext, - UseField, - ToggleField, - NumericField, -} from '../../../../../../shared_imports'; +import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; import { useEditPolicyContext } from '../../../edit_policy_context'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../'; +import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../'; -import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared'; +import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared_fields'; const i18nTexts = { dataTierAllocation: { @@ -43,15 +37,13 @@ const formFieldPaths = { }; export const ColdPhase: FunctionComponent = () => { - const { originalPolicy } = useEditPolicyContext(); - const form = useFormContext(); + const { policy } = useEditPolicyContext(); const [formData] = useFormData({ watch: [formFieldPaths.enabled], }); const enabled = get(formData, formFieldPaths.enabled); - const isShowingErrors = form.isValid === false; return (
@@ -66,8 +58,7 @@ export const ColdPhase: FunctionComponent = () => { defaultMessage="Cold phase" />

{' '} - {enabled && !isShowingErrors ? : null} - + {enabled && } } titleSize="s" @@ -128,9 +119,7 @@ export const ColdPhase: FunctionComponent = () => { 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } ), - initialValue: Boolean( - originalPolicy.phases.cold?.actions?.allocate?.number_of_replicas - ), + initialValue: Boolean(policy.phases.cold?.actions?.allocate?.number_of_replicas), }} fullWidth > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx similarity index 50% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx index 78ae66327654c..37323b97edc92 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx @@ -7,53 +7,24 @@ import React, { FunctionComponent, Fragment } from 'react'; import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiTextColor, EuiFormRow } from '@elastic/eui'; -import { DeletePhase as DeletePhaseInterface, Phases } from '../../../../../../common/types'; +import { useFormData, UseField, ToggleField } from '../../../../../../shared_imports'; -import { useFormData } from '../../../../../shared_imports'; +import { ActiveBadge, LearnMoreLink, OptionalLabel } from '../../index'; -import { PhaseValidationErrors } from '../../../../services/policies/policy_validation'; +import { MinAgeInputField, SnapshotPoliciesField } from '../shared_fields'; -import { - ActiveBadge, - LearnMoreLink, - OptionalLabel, - PhaseErrorMessage, - MinAgeInput, - SnapshotPolicies, -} from '../'; -import { useRolloverPath } from './shared'; - -const deleteProperty: keyof Phases = 'delete'; -const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName; - -interface Props { - setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void; - phaseData: DeletePhaseInterface; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; - getUrlForApp: ( - appId: string, - options?: { - path?: string; - absolute?: boolean; - } - ) => string; -} +const formFieldPaths = { + enabled: '_meta.delete.enabled', +}; -export const DeletePhase: FunctionComponent = ({ - setPhaseData, - phaseData, - errors, - isShowingErrors, - getUrlForApp, -}) => { +export const DeletePhase: FunctionComponent = () => { const [formData] = useFormData({ - watch: useRolloverPath, + watch: formFieldPaths.enabled, }); - const hotPhaseRolloverEnabled = get(formData, useRolloverPath); + const enabled = get(formData, formFieldPaths.enabled); return (
@@ -66,8 +37,7 @@ export const DeletePhase: FunctionComponent = ({ defaultMessage="Delete phase" /> {' '} - {phaseData.phaseEnabled && !isShowingErrors ? : null} - + {enabled && }
} titleSize="s" @@ -79,39 +49,23 @@ export const DeletePhase: FunctionComponent = ({ defaultMessage="You no longer need your index. You can define when it is safe to delete it." />

- - } - id={`${deleteProperty}-${phaseProperty('phaseEnabled')}`} - checked={phaseData.phaseEnabled} - onChange={(e) => { - setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); +
} fullWidth > - {phaseData.phaseEnabled ? ( - - errors={errors} - phaseData={phaseData} - phase={deleteProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - rolloverEnabled={hotPhaseRolloverEnabled} - /> - ) : ( -
- )} + {enabled && } - {phaseData.phaseEnabled ? ( + {enabled ? ( @@ -145,11 +99,7 @@ export const DeletePhase: FunctionComponent = ({ } > - setPhaseData(phaseProperty('waitForSnapshotPolicy'), value)} - getUrlForApp={getUrlForApp} - /> + ) : null} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts new file mode 100644 index 0000000000000..488e4e26cfce0 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { DeletePhase } from './delete_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx index a184ddf5148b9..629c1388f61fb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx @@ -19,7 +19,6 @@ import { import { Phases } from '../../../../../../../common/types'; import { - useFormContext, useFormData, UseField, SelectField, @@ -29,26 +28,24 @@ import { import { i18nTexts } from '../../../i18n_texts'; -import { ROLLOVER_EMPTY_VALIDATION } from '../../../form_validations'; +import { ROLLOVER_EMPTY_VALIDATION } from '../../../form'; import { ROLLOVER_FORM_PATHS } from '../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../'; +import { LearnMoreLink, ActiveBadge } from '../../'; -import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared'; +import { Forcemerge, SetPriorityInput, useRolloverPath } from '../shared_fields'; import { maxSizeStoredUnits, maxAgeUnits } from './constants'; const hotProperty: keyof Phases = 'hot'; export const HotPhase: FunctionComponent = () => { - const form = useFormContext(); const [formData] = useFormData({ watch: useRolloverPath, }); const isRolloverEnabled = get(formData, useRolloverPath); - const isShowingErrors = form.isValid === false; const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); return ( @@ -62,8 +59,7 @@ export const HotPhase: FunctionComponent = () => { defaultMessage="Hot phase" /> {' '} - {isShowingErrors ? null : } - +
} titleSize="s" diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/cloud_data_tier_callout.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.scss diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/default_allocation_notice.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/no_node_attributes_warning.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx index 407bb9ea92e85..c1676d7074dbc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx @@ -10,12 +10,8 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; -import { PhaseWithAllocationAction } from '../../../../../../../../../common/types'; - import { UseField, SelectField, useFormData } from '../../../../../../../../shared_imports'; -import { propertyof } from '../../../../../../../services/policies/policy_validation'; - import { LearnMoreLink } from '../../../../learn_more_link'; import { NodeAttrsDetails } from './node_attrs_details'; @@ -61,9 +57,6 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes }) nodeOptions.sort((a, b) => a.value.localeCompare(b.value)); - // check that this string is a valid property - const nodeAttrsProperty = propertyof('selectedNodeAttrs'); - return ( <> @@ -100,7 +93,7 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes }) ) : undefined, euiFieldProps: { - 'data-test-subj': `${phase}-${nodeAttrsProperty}`, + 'data-test-subj': `${phase}-selectedNodeAttrs`, options: [{ text: i18nTexts.doNotModifyAllocationOption, value: '' }].concat( nodeOptions ), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_attrs_details.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_data_provider.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/types.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/data_tier_allocation_field.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/index.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx similarity index 94% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx index b410bd0e6b3b0..b05d49be497cd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx @@ -21,11 +21,11 @@ interface Props { } export const Forcemerge: React.FunctionComponent = ({ phase }) => { - const { originalPolicy } = useEditPolicyContext(); + const { policy } = useEditPolicyContext(); const initialToggleValue = useMemo(() => { - return Boolean(originalPolicy.phases[phase]?.actions?.forcemerge); - }, [originalPolicy, phase]); + return Boolean(policy.phases[phase]?.actions?.forcemerge); + }, [policy, phase]); return ( = ({ phase }) => { - const phaseIndexPriorityProperty = propertyof('phaseIndexPriority'); return ( = ({ phase }) => { componentProps={{ fullWidth: false, euiFieldProps: { - 'data-test-subj': `${phase}-${phaseIndexPriorityProperty}`, - min: 1, + 'data-test-subj': `${phase}-phaseIndexPriority`, + min: 0, }, }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx similarity index 68% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx index cc2849b5c8e9c..e9f9f331e410a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx @@ -4,52 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; - +import React from 'react'; +import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ApplicationStart } from 'kibana/public'; import { EuiButtonIcon, EuiCallOut, - EuiComboBox, EuiComboBoxOptionOption, EuiLink, EuiSpacer, } from '@elastic/eui'; -import { useLoadSnapshotPolicies } from '../../../services/api'; +import { UseField, ComboBoxField, useFormData } from '../../../../../../shared_imports'; +import { useLoadSnapshotPolicies } from '../../../../../services/api'; +import { useEditPolicyContext } from '../../../edit_policy_context'; + +const waitForSnapshotFormField = 'phases.delete.actions.wait_for_snapshot.policy'; -interface Props { - value: string; - onChange: (value: string) => void; - getUrlForApp: ApplicationStart['getUrlForApp']; -} -export const SnapshotPolicies: React.FunctionComponent = ({ - value, - onChange, - getUrlForApp, -}) => { +export const SnapshotPoliciesField: React.FunctionComponent = () => { + const { getUrlForApp } = useEditPolicyContext(); const { error, isLoading, data, resendRequest } = useLoadSnapshotPolicies(); + const [formData] = useFormData({ + watch: waitForSnapshotFormField, + }); + + const selectedSnapshotPolicy = get(formData, waitForSnapshotFormField); const policies = data.map((name: string) => ({ label: name, value: name, })); - const onComboChange = (options: EuiComboBoxOptionOption[]) => { - if (options.length > 0) { - onChange(options[0].label); - } else { - onChange(''); - } - }; - - const onCreateOption = (newValue: string) => { - onChange(newValue); - }; - const getUrlForSnapshotPolicyWizard = () => { return getUrlForApp('management', { path: `data/snapshot_restore/add_policy`, @@ -59,14 +46,14 @@ export const SnapshotPolicies: React.FunctionComponent = ({ let calloutContent; if (error) { calloutContent = ( - + <> + <> = ({ } )} /> - + } > = ({ defaultMessage="Refresh this field and enter the name of an existing snapshot policy." /> - + ); } else if (data.length === 0) { calloutContent = ( - + <> = ({ }} /> - + ); - } else if (value && !data.includes(value)) { + } else if (selectedSnapshotPolicy && !data.includes(selectedSnapshotPolicy)) { calloutContent = ( - + <> = ({ }} /> - + ); } return ( - - + path={waitForSnapshotFormField}> + {(field) => { + const singleSelectionArray: [selectedSnapshot?: string] = field.value + ? [field.value] + : []; + + return ( + { + field.setValue(newOption); }, - ] - : [] - } - onChange={onComboChange} - noSuggestions={!!(error || data.length === 0)} - /> + onChange: (options: EuiComboBoxOptionOption[]) => { + if (options.length > 0) { + field.setValue(options[0].label); + } else { + field.setValue(''); + } + }, + }} + /> + ); + }} + {calloutContent} - + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 06c16e8bdd5ab..94fd2ee9edaca 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -17,23 +17,17 @@ import { EuiDescribedFormGroup, } from '@elastic/eui'; -import { - useFormData, - UseField, - ToggleField, - useFormContext, - NumericField, -} from '../../../../../../shared_imports'; +import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; import { Phases } from '../../../../../../../common/types'; -import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared'; +import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared_fields'; import { useEditPolicyContext } from '../../../edit_policy_context'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../'; +import { LearnMoreLink, ActiveBadge, DescribedFormField } from '../../'; -import { DataTierAllocationField } from '../shared'; +import { DataTierAllocationField } from '../shared_fields'; const i18nTexts = { shrinkLabel: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', { @@ -54,8 +48,7 @@ const formFieldPaths = { }; export const WarmPhase: FunctionComponent = () => { - const { originalPolicy } = useEditPolicyContext(); - const form = useFormContext(); + const { policy } = useEditPolicyContext(); const [formData] = useFormData({ watch: [useRolloverPath, formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], }); @@ -63,7 +56,6 @@ export const WarmPhase: FunctionComponent = () => { const enabled = get(formData, formFieldPaths.enabled); const hotPhaseRolloverEnabled = get(formData, useRolloverPath); const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover); - const isShowingErrors = form.isValid === false; return (
@@ -77,8 +69,7 @@ export const WarmPhase: FunctionComponent = () => { defaultMessage="Warm phase" /> {' '} - {enabled && !isShowingErrors ? : null} - + {enabled && }
} titleSize="s" @@ -161,9 +152,7 @@ export const WarmPhase: FunctionComponent = () => { 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } ), - initialValue: Boolean( - originalPolicy.phases.warm?.actions?.allocate?.number_of_replicas - ), + initialValue: Boolean(policy.phases.warm?.actions?.allocate?.number_of_replicas), }} fullWidth > @@ -203,7 +192,7 @@ export const WarmPhase: FunctionComponent = () => { 'data-test-subj': 'shrinkSwitch', label: i18nTexts.shrinkLabel, 'aria-label': i18nTexts.shrinkLabel, - initialValue: Boolean(originalPolicy.phases.warm?.actions?.shrink), + initialValue: Boolean(policy.phases.warm?.actions?.shrink), }} fullWidth > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index 7098b018d6dfd..a8b1680ebde07 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -7,7 +7,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - import { EuiButtonEmpty, EuiCodeBlock, @@ -25,19 +24,15 @@ import { import { SerializedPolicy } from '../../../../../common/types'; import { useFormContext, useFormData } from '../../../../shared_imports'; + import { FormInternal } from '../types'; interface Props { - legacyPolicy: SerializedPolicy; close: () => void; policyName: string; } -export const PolicyJsonFlyout: React.FunctionComponent = ({ - policyName, - close, - legacyPolicy, -}) => { +export const PolicyJsonFlyout: React.FunctionComponent = ({ policyName, close }) => { /** * policy === undefined: we are checking validity * policy === null: we have determined the policy is invalid @@ -51,20 +46,11 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ const updatePolicy = useCallback(async () => { setPolicy(undefined); if (await validateForm()) { - const p = getFormData() as SerializedPolicy; - setPolicy({ - ...legacyPolicy, - phases: { - ...legacyPolicy.phases, - hot: p.phases.hot, - warm: p.phases.warm, - cold: p.phases.cold, - }, - }); + setPolicy(getFormData() as SerializedPolicy); } else { setPolicy(null); } - }, [setPolicy, getFormData, legacyPolicy, validateForm]); + }, [setPolicy, getFormData, validateForm]); useEffect(() => { updatePolicy(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx index c82a420b74857..ebef80871b83d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx @@ -12,8 +12,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../shared_imports'; import { useLoadPoliciesList } from '../../services/api'; +import { getPolicyByName } from '../../lib/policies'; +import { defaultPolicy } from '../../constants'; import { EditPolicy as PresentationComponent } from './edit_policy'; +import { EditPolicyContextProvider } from './edit_policy_context'; interface RouterProps { policyName: string; @@ -44,6 +47,7 @@ export const EditPolicy: React.FunctionComponent { breadcrumbService.setBreadcrumbs('editPolicy'); }, [breadcrumbService]); + if (isLoading) { return ( + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index 5397f5da2d6bb..1abbe884c2dc2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react'; +import React, { Fragment, useEffect, useState, useMemo } from 'react'; +import { get } from 'lodash'; import { RouteComponentProps } from 'react-router-dom'; @@ -16,7 +17,6 @@ import { EuiButton, EuiButtonEmpty, EuiDescribedFormGroup, - EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -30,31 +30,13 @@ import { EuiTitle, } from '@elastic/eui'; -import { useForm, Form } from '../../../shared_imports'; +import { useForm, Form, UseField, TextField, useFormData } from '../../../shared_imports'; import { toasts } from '../../services/notification'; -import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types'; - -import { defaultPolicy } from '../../constants'; - -import { - validatePolicy, - ValidationErrors, - findFirstError, -} from '../../services/policies/policy_validation'; - -import { savePolicy } from '../../services/policies/policy_save'; +import { savePolicy } from './save_policy'; import { - deserializePolicy, - getPolicyByName, - initializeNewPolicy, - legacySerializePolicy, -} from '../../services/policies/policy_serialization'; - -import { - ErrableFormRow, LearnMoreLink, PolicyJsonFlyout, ColdPhase, @@ -63,93 +45,66 @@ import { WarmPhase, } from './components'; -import { schema } from './form_schema'; -import { deserializer } from './deserializer'; -import { createSerializer } from './serializer'; +import { schema, deserializer, createSerializer, createPolicyNameValidations } from './form'; -import { EditPolicyContextProvider } from './edit_policy_context'; +import { useEditPolicyContext } from './edit_policy_context'; +import { FormInternal } from './types'; export interface Props { - policies: PolicyFromES[]; - policyName: string; - getUrlForApp: ( - appId: string, - options?: { - path?: string; - absolute?: boolean; - } - ) => string; history: RouteComponentProps['history']; } -const mergeAllSerializedPolicies = ( - serializedPolicy: SerializedPolicy, - legacySerializedPolicy: SerializedPolicy -): SerializedPolicy => { - return { - ...legacySerializedPolicy, - phases: { - ...legacySerializedPolicy.phases, - hot: serializedPolicy.phases.hot, - warm: serializedPolicy.phases.warm, - cold: serializedPolicy.phases.cold, - }, - }; -}; +const policyNamePath = 'name'; -export const EditPolicy: React.FunctionComponent = ({ - policies, - policyName, - history, - getUrlForApp, -}) => { +export const EditPolicy: React.FunctionComponent = ({ history }) => { useEffect(() => { window.scrollTo(0, 0); }, []); - const [isShowingErrors, setIsShowingErrors] = useState(false); - const [errors, setErrors] = useState(); const [isShowingPolicyJsonFlyout, setIsShowingPolicyJsonFlyout] = useState(false); - - const existingPolicy = getPolicyByName(policies, policyName); + const { + isNewPolicy, + policy: currentPolicy, + existingPolicies, + policyName, + } = useEditPolicyContext(); const serializer = useMemo(() => { - return createSerializer(existingPolicy?.policy); - }, [existingPolicy?.policy]); + return createSerializer(isNewPolicy ? undefined : currentPolicy); + }, [isNewPolicy, currentPolicy]); - const originalPolicy = existingPolicy?.policy ?? defaultPolicy; + const [saveAsNew, setSaveAsNew] = useState(isNewPolicy); + const originalPolicyName: string = isNewPolicy ? '' : policyName!; const { form } = useForm({ schema, - defaultValue: originalPolicy, + defaultValue: { + ...currentPolicy, + name: originalPolicyName, + }, deserializer, serializer, }); - const [policy, setPolicy] = useState(() => - existingPolicy ? deserializePolicy(existingPolicy) : initializeNewPolicy(policyName) + const [formData] = useFormData({ form, watch: policyNamePath }); + const currentPolicyName = get(formData, policyNamePath); + + const policyNameValidations = useMemo( + () => + createPolicyNameValidations({ + originalPolicyName, + policies: existingPolicies, + saveAsNewPolicy: saveAsNew, + }), + [originalPolicyName, existingPolicies, saveAsNew] ); - const isNewPolicy: boolean = !Boolean(existingPolicy); - const [saveAsNew, setSaveAsNew] = useState(isNewPolicy); - const originalPolicyName: string = existingPolicy ? existingPolicy.name : ''; - const backToPolicyList = () => { history.push('/policies'); }; const submit = async () => { - setIsShowingErrors(true); - const { data: formLibPolicy, isValid: newIsValid } = await form.submit(); - const [legacyIsValid, validationErrors] = validatePolicy( - saveAsNew, - policy, - policies, - originalPolicyName - ); - setErrors(validationErrors); - - const isValid = legacyIsValid && newIsValid; + const { data: policy, isValid } = await form.submit(); if (!isValid) { toasts.addDanger( @@ -157,22 +112,11 @@ export const EditPolicy: React.FunctionComponent = ({ defaultMessage: 'Please fix the errors on this page.', }) ); - // This functionality will not be required for once form lib is fully adopted for this form - // because errors are reported as fields are edited. - if (!legacyIsValid) { - const firstError = findFirstError(validationErrors); - const errorRowId = `${firstError ? firstError.replace('.', '-') : ''}-row`; - const element = document.getElementById(errorRowId); - if (element) { - element.scrollIntoView({ block: 'center', inline: 'nearest' }); - } - } } else { - const readSerializedPolicy = () => { - const legacySerializedPolicy = legacySerializePolicy(policy, existingPolicy?.policy); - return mergeAllSerializedPolicies(formLibPolicy, legacySerializedPolicy); - }; - const success = await savePolicy(readSerializedPolicy, isNewPolicy || saveAsNew); + const success = await savePolicy( + { ...policy, name: saveAsNew ? currentPolicyName : originalPolicyName }, + isNewPolicy || saveAsNew + ); if (success) { backToPolicyList(); } @@ -183,248 +127,217 @@ export const EditPolicy: React.FunctionComponent = ({ setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout); }; - const setPhaseData = useCallback( - (phase: keyof LegacyPolicy['phases'], key: string, value: any) => { - setPolicy((nextPolicy) => ({ - ...nextPolicy, - phases: { - ...nextPolicy.phases, - [phase]: { ...nextPolicy.phases[phase], [key]: value }, - }, - })); - }, - [setPolicy] - ); - - const setDeletePhaseData = useCallback( - (key: string, value: any) => setPhaseData('delete', key, value), - [setPhaseData] - ); - return ( - - - - - -

- {isNewPolicy - ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { - defaultMessage: 'Create an index lifecycle policy', - }) - : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { - defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', - values: { originalPolicyName }, - })} -

-
- -
-
- - -

- + +

+ {isNewPolicy + ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { + defaultMessage: 'Create an index lifecycle policy', + }) + : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { + defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', + values: { originalPolicyName }, + })} +

+ + +
+ + + +

+ {' '} - - } - /> -

-
+ />{' '} + + } + /> +

+ - + - {isNewPolicy ? null : ( - - -

- - - - .{' '} + {isNewPolicy ? null : ( + + +

+ + + .{' '} + -

-
- - - - { - setSaveAsNew(e.target.checked); - }} - label={ - - - - } /> - -
- )} - - {saveAsNew || isNewPolicy ? ( - - +

+ + + + + { + setSaveAsNew(e.target.checked); + }} + label={ + -
- } - titleSize="s" - fullWidth - > - + + + )} + + {saveAsNew || isNewPolicy ? ( + + - } - > - { - setPolicy({ ...policy, name: e.target.value }); - }} - /> - - - ) : null} + +
+ } + titleSize="s" + fullWidth + > + + path={policyNamePath} + config={{ + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', { + defaultMessage: 'Policy name', + }), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage', + { + defaultMessage: + 'A policy name cannot start with an underscore and cannot contain a question mark or a space.', + } + ), + validations: policyNameValidations, + }} + component={TextField} + componentProps={{ + fullWidth: false, + euiFieldProps: { + 'data-test-subj': 'policyNameField', + }, + }} + /> +
+ ) : null} - + - + - + - + - + - + - + - 0 - } - getUrlForApp={getUrlForApp} - setPhaseData={setDeletePhaseData} - phaseData={policy.phases.delete} - /> + - - - - - - - - {saveAsNew ? ( - - ) : ( - - )} - - - - - + + + + + + + + {saveAsNew ? ( - - - - - - - - {isShowingPolicyJsonFlyout ? ( - - ) : ( + ) : ( + + )} + + + + + - )} - - - - - {isShowingPolicyJsonFlyout ? ( - setIsShowingPolicyJsonFlyout(false)} - /> - ) : null} - - - - - - + + + + + + + + {isShowingPolicyJsonFlyout ? ( + + ) : ( + + )} + + + + + {isShowingPolicyJsonFlyout ? ( + setIsShowingPolicyJsonFlyout(false)} + /> + ) : null} + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx index 4748c26d6cec1..da5f940b1b6c8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx @@ -5,10 +5,16 @@ */ import React, { createContext, ReactChild, useContext } from 'react'; -import { SerializedPolicy } from '../../../../common/types'; +import { ApplicationStart } from 'kibana/public'; -interface EditPolicyContextValue { - originalPolicy: SerializedPolicy; +import { PolicyFromES, SerializedPolicy } from '../../../../common/types'; + +export interface EditPolicyContextValue { + isNewPolicy: boolean; + policy: SerializedPolicy; + existingPolicies: PolicyFromES[]; + getUrlForApp: ApplicationStart['getUrlForApp']; + policyName?: string; } const EditPolicyContext = createContext(null as any); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts similarity index 82% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index f0294a5391d21..5af8807f2dec8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -6,17 +6,17 @@ import { produce } from 'immer'; -import { SerializedPolicy } from '../../../../common/types'; +import { SerializedPolicy } from '../../../../../common/types'; -import { splitSizeAndUnits } from '../../services/policies/policy_serialization'; +import { splitSizeAndUnits } from '../../../lib/policies'; -import { determineDataTierAllocationType } from '../../lib'; +import { determineDataTierAllocationType } from '../../../lib'; -import { FormInternal } from './types'; +import { FormInternal } from '../types'; export const deserializer = (policy: SerializedPolicy): FormInternal => { const { - phases: { hot, warm, cold }, + phases: { hot, warm, cold, delete: deletePhase }, } = policy; const _meta: FormInternal['_meta'] = { @@ -37,6 +37,9 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { dataTierAllocationType: determineDataTierAllocationType(cold?.actions), freezeEnabled: Boolean(cold?.actions?.freeze), }, + delete: { + enabled: Boolean(deletePhase), + }, }; return produce( @@ -86,6 +89,14 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { draft._meta.cold.minAgeUnit = minAge.units; } } + + if (draft.phases.delete) { + if (draft.phases.delete.min_age) { + const minAge = splitSizeAndUnits(draft.phases.delete.min_age); + draft.phases.delete.min_age = minAge.size; + draft._meta.delete.minAgeUnit = minAge.units; + } + } } ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts new file mode 100644 index 0000000000000..82fa478832582 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { deserializer } from './deserializer'; + +export { createSerializer } from './serializer'; + +export { schema } from './schema'; + +export * from './validations'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 070f03f74b954..4d20db4018740 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -6,18 +6,19 @@ import { i18n } from '@kbn/i18n'; -import { FormSchema, fieldValidators } from '../../../shared_imports'; -import { defaultSetPriority, defaultPhaseIndexPriority } from '../../constants'; +import { FormSchema, fieldValidators } from '../../../../shared_imports'; +import { defaultSetPriority, defaultPhaseIndexPriority } from '../../../constants'; -import { FormInternal } from './types'; +import { FormInternal } from '../types'; import { ifExistsNumberGreaterThanZero, ifExistsNumberNonNegative, rolloverThresholdsValidator, -} from './form_validations'; + minAgeValidator, +} from './validations'; -import { i18nTexts } from './i18n_texts'; +import { i18nTexts } from '../i18n_texts'; const { emptyField, numberGreaterThanField } = fieldValidators; @@ -97,6 +98,18 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, }, + delete: { + enabled: { + defaultValue: false, + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.deletePhase.activateWarmPhaseSwitchLabel', + { defaultMessage: 'Activate delete phase' } + ), + }, + minAgeUnit: { + defaultValue: 'd', + }, + }, }, phases: { hot: { @@ -177,15 +190,7 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: (arg) => - numberGreaterThanField({ - than: 0, - allowEquality: true, - message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, - })({ - ...arg, - value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), - }), + validator: minAgeValidator, }, ], }, @@ -256,15 +261,7 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: (arg) => - numberGreaterThanField({ - than: 0, - allowEquality: true, - message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, - })({ - ...arg, - value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), - }), + validator: minAgeValidator, }, ], }, @@ -292,5 +289,15 @@ export const schema: FormSchema = { }, }, }, + delete: { + min_age: { + defaultValue: '0', + validations: [ + { + validator: minAgeValidator, + }, + ], + }, + }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts similarity index 90% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts index 564b5a2c4e397..2274efda426ad 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash'; +import { isEmpty, isNumber } from 'lodash'; -import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../common/types'; +import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../../common/types'; -import { FormInternal, DataAllocationMetaFields } from './types'; -import { isNumber } from '../../services/policies/policy_serialization'; +import { FormInternal, DataAllocationMetaFields } from '../types'; const serializeAllocateAction = ( { dataTierAllocationType, allocationNodeAttribute }: DataAllocationMetaFields, @@ -165,5 +164,22 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( } } + /** + * DELETE PHASE SERIALIZATION + */ + if (policy.phases.delete) { + if (policy.phases.delete.min_age) { + policy.phases.delete.min_age = `${policy.phases.delete.min_age}${_meta.delete.minAgeUnit}`; + } + + if (originalPolicy?.phases.delete?.actions) { + const { wait_for_snapshot: __, ...rest } = originalPolicy.phases.delete.actions; + policy.phases.delete.actions = { + ...policy.phases.delete.actions, + ...rest, + }; + } + } + return policy; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts similarity index 50% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts index 9c855ccb41624..f2e26a552efc9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fieldValidators, ValidationFunc } from '../../../shared_imports'; +import { fieldValidators, ValidationFunc, ValidationConfig } from '../../../../shared_imports'; -import { ROLLOVER_FORM_PATHS } from './constants'; +import { ROLLOVER_FORM_PATHS } from '../constants'; -import { i18nTexts } from './i18n_texts'; +import { i18nTexts } from '../i18n_texts'; +import { PolicyFromES } from '../../../../../common/types'; +import { FormInternal } from '../types'; -const { numberGreaterThanField } = fieldValidators; +const { numberGreaterThanField, containsCharsField, emptyField, startsWithField } = fieldValidators; const createIfNumberExistsValidator = ({ than, @@ -46,7 +48,7 @@ export const ifExistsNumberNonNegative = createIfNumberExistsValidator({ * A special validation type used to keep track of validation errors for * the rollover threshold values not being set (e.g., age and doc count) */ -export const ROLLOVER_EMPTY_VALIDATION = 'EMPTY'; +export const ROLLOVER_EMPTY_VALIDATION = 'ROLLOVER_EMPTY_VALIDATION'; /** * An ILM policy requires that for rollover a value must be set for one of the threshold values. @@ -87,3 +89,68 @@ export const rolloverThresholdsValidator: ValidationFunc = ({ form }) => { fields[ROLLOVER_FORM_PATHS.maxSize].clearErrors(ROLLOVER_EMPTY_VALIDATION); } }; + +export const minAgeValidator: ValidationFunc = (arg) => + numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, + })({ + ...arg, + value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), + }); + +export const createPolicyNameValidations = ({ + policies, + saveAsNewPolicy, + originalPolicyName, +}: { + policies: PolicyFromES[]; + saveAsNewPolicy: boolean; + originalPolicyName?: string; +}): Array> => { + return [ + { + validator: emptyField(i18nTexts.editPolicy.errors.policyNameRequiredMessage), + }, + { + validator: startsWithField({ + message: i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, + char: '_', + }), + }, + { + validator: containsCharsField({ + message: i18nTexts.editPolicy.errors.policyNameContainsInvalidChars, + chars: [',', ' '], + }), + }, + { + validator: (arg) => { + const policyName = arg.value; + if (window.TextEncoder && new window.TextEncoder().encode(policyName).length > 255) { + return { + message: i18nTexts.editPolicy.errors.policyNameTooLongErrorMessage, + }; + } + }, + }, + { + validator: (arg) => { + const policyName = arg.value; + if (saveAsNewPolicy && policyName === originalPolicyName) { + return { + message: i18nTexts.editPolicy.errors.policyNameMustBeDifferentErrorMessage, + }; + } else if (policyName !== originalPolicyName) { + const policyNames = policies.map((existingPolicy) => existingPolicy.name); + if (policyNames.includes(policyName)) { + return { + message: i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, + }; + } + } + }, + }, + ]; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 1fba69b7634ae..ccd5d3a568fe3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -98,6 +98,42 @@ export const i18nTexts = { defaultMessage: 'Only non-negative numbers are allowed.', } ), + policyNameContainsInvalidChars: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.errors.policyNameContainsInvalidCharsError', + { + defaultMessage: 'A policy name cannot contain spaces or commas.', + } + ), + policyNameAlreadyUsedErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError', + { + defaultMessage: 'That policy name is already used.', + } + ), + policyNameMustBeDifferentErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError', + { + defaultMessage: 'The policy name must be different.', + } + ), + policyNameRequiredMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError', + { + defaultMessage: 'A policy name is required.', + } + ), + policyNameStartsWithUnderscoreErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError', + { + defaultMessage: 'A policy name cannot start with an underscore.', + } + ), + policyNameTooLongErrorMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError', + { + defaultMessage: 'A policy name cannot be longer than 255 bytes.', + } + ), }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts similarity index 84% rename from x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts index 9cf622e830cb2..e2ab6a8817ef6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_save.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/save_policy.ts @@ -3,23 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { SerializedPolicy } from '../../../../common/types'; -import { savePolicy as savePolicyApi } from '../api'; -import { showApiError } from '../api_errors'; -import { getUiMetricsForPhases, trackUiMetric } from '../ui_metric'; + import { UIM_POLICY_CREATE, UIM_POLICY_UPDATE } from '../../constants'; -import { toasts } from '../notification'; + +import { toasts } from '../../services/notification'; +import { savePolicy as savePolicyApi } from '../../services/api'; +import { getUiMetricsForPhases, trackUiMetric } from '../../services/ui_metric'; +import { showApiError } from '../../services/api_errors'; export const savePolicy = async ( - readSerializedPolicy: () => SerializedPolicy, + serializedPolicy: SerializedPolicy, isNew: boolean ): Promise => { - const serializedPolicy = readSerializedPolicy(); - try { await savePolicyApi(serializedPolicy); } catch (err) { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 1884f8dbc0619..dc3d8a640e682 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -38,6 +38,10 @@ interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { freezeEnabled: boolean; } +interface DeletePhaseMetaFields extends MinAgeField { + enabled: boolean; +} + /** * Describes the shape of data after deserialization. */ @@ -50,5 +54,6 @@ export interface FormInternal extends SerializedPolicy { hot: HotPhaseMetaFields; warm: WarmPhaseMetaFields; cold: ColdPhaseMetaFields; + delete: DeletePhaseMetaFields; }; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts deleted file mode 100644 index 6ada039d45cd9..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/delete_phase.ts +++ /dev/null @@ -1,88 +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 { DeletePhase, SerializedDeletePhase } from '../../../../common/types'; -import { serializedPhaseInitialization } from '../../constants'; -import { isNumber, splitSizeAndUnits } from './policy_serialization'; -import { - numberRequiredMessage, - PhaseValidationErrors, - positiveNumberRequiredMessage, -} from './policy_validation'; - -const deletePhaseInitialization: DeletePhase = { - phaseEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - waitForSnapshotPolicy: '', -}; - -export const deletePhaseFromES = (phaseSerialized?: SerializedDeletePhase): DeletePhase => { - const phase = { ...deletePhaseInitialization }; - if (phaseSerialized === undefined || phaseSerialized === null) { - return phase; - } - - phase.phaseEnabled = true; - if (phaseSerialized.min_age) { - const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age); - phase.selectedMinimumAge = minAge; - phase.selectedMinimumAgeUnits = minAgeUnits; - } - - if (phaseSerialized.actions) { - const actions = phaseSerialized.actions; - - if (actions.wait_for_snapshot) { - phase.waitForSnapshotPolicy = actions.wait_for_snapshot.policy; - } - } - - return phase; -}; - -export const deletePhaseToES = ( - phase: DeletePhase, - originalEsPhase?: SerializedDeletePhase -): SerializedDeletePhase => { - if (!originalEsPhase) { - originalEsPhase = { ...serializedPhaseInitialization }; - } - const esPhase = { ...originalEsPhase }; - - if (isNumber(phase.selectedMinimumAge)) { - esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; - } - - esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {}; - - if (phase.waitForSnapshotPolicy) { - esPhase.actions.wait_for_snapshot = { - policy: phase.waitForSnapshotPolicy, - }; - } else { - delete esPhase.actions.wait_for_snapshot; - } - - return esPhase; -}; - -export const validateDeletePhase = (phase: DeletePhase): PhaseValidationErrors => { - if (!phase.phaseEnabled) { - return {}; - } - - const phaseErrors = {} as PhaseValidationErrors; - - // min age needs to be a positive number - if (!isNumber(phase.selectedMinimumAge)) { - phaseErrors.selectedMinimumAge = [numberRequiredMessage]; - } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { - phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; - } - - return { ...phaseErrors }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts deleted file mode 100644 index 19481b39a2c80..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts +++ /dev/null @@ -1,198 +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. - */ -// Prefer importing entire lodash library, e.g. import { get } from "lodash" -// eslint-disable-next-line no-restricted-imports -import cloneDeep from 'lodash/cloneDeep'; -import { deserializePolicy, legacySerializePolicy } from './policy_serialization'; -import { defaultNewDeletePhase } from '../../constants'; - -describe('Policy serialization', () => { - test('serialize a policy using "default" data allocation', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "custom" data allocation', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "custom" data allocation with no node attributes', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - // There should be no allocation action in any phases... - name: 'test', - phases: {}, - }); - }); - - test('serialize a policy using "none" data allocation with no node attributes', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - // There should be no allocation action in any phases... - name: 'test', - phases: {}, - }); - }); - - test('serialization does not alter the original policy', () => { - const originalPolicy = { - name: 'test', - phases: {}, - }; - - const originalClone = cloneDeep(originalPolicy); - - const deserializedPolicy = { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }; - - legacySerializePolicy(deserializedPolicy, originalPolicy); - expect(originalPolicy).toEqual(originalClone); - }); - - test('serialize a policy using "best_compression" codec for forcemerge', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: { - hot: { actions: {} }, - }, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); - - test('de-serialize a policy using "best_compression" codec for forcemerge', () => { - expect( - deserializePolicy({ - modified_date: Date.now().toString(), - name: 'test', - version: 1, - policy: { - name: 'test', - phases: { - hot: { - actions: { - rollover: { - max_age: '30d', - max_size: '50gb', - }, - forcemerge: { - max_num_segments: 1, - index_codec: 'best_compression', - }, - set_priority: { - priority: 100, - }, - }, - }, - }, - }, - }) - ).toEqual({ - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }); - }); - - test('delete "best_compression" codec for forcemerge if disabled in UI', () => { - expect( - legacySerializePolicy( - { - name: 'test', - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }, - { - name: 'test', - phases: {}, - } - ) - ).toEqual({ - name: 'test', - phases: {}, - }); - }); -}); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts deleted file mode 100644 index 55e9d88dcd383..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts +++ /dev/null @@ -1,82 +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 { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types'; - -import { defaultNewDeletePhase, serializedPhaseInitialization } from '../../constants'; - -import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; - -export const splitSizeAndUnits = (field: string): { size: string; units: string } => { - let size = ''; - let units = ''; - - const result = /(\d+)(\w+)/.exec(field); - if (result) { - size = result[1]; - units = result[2]; - } - - return { - size, - units, - }; -}; - -export const isNumber = (value: any): boolean => value !== '' && value !== null && isFinite(value); - -export const getPolicyByName = ( - policies: PolicyFromES[] | null | undefined, - policyName: string = '' -): PolicyFromES | undefined => { - if (policies && policies.length > 0) { - return policies.find((policy: PolicyFromES) => policy.name === policyName); - } -}; - -export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => { - return { - name: newPolicyName, - phases: { - delete: { ...defaultNewDeletePhase }, - }, - }; -}; - -export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => { - const { - name, - policy: { phases }, - } = policy; - - return { - name, - phases: { - delete: deletePhaseFromES(phases.delete), - }, - }; -}; - -export const legacySerializePolicy = ( - policy: LegacyPolicy, - originalEsPolicy: SerializedPolicy = { - name: policy.name, - phases: { hot: { ...serializedPhaseInitialization } }, - } -): SerializedPolicy => { - const serializedPolicy = { - name: policy.name, - phases: {}, - } as SerializedPolicy; - - if (policy.phases.delete.phaseEnabled) { - serializedPolicy.phases.delete = deletePhaseToES( - policy.phases.delete, - originalEsPolicy.phases.delete - ); - } - return serializedPolicy; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts deleted file mode 100644 index 79c909c433f33..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts +++ /dev/null @@ -1,144 +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 { i18n } from '@kbn/i18n'; -import { DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types'; -import { validateDeletePhase } from './delete_phase'; - -export const propertyof = (propertyName: keyof T & string) => propertyName; - -export const numberRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredError', - { - defaultMessage: 'A number is required.', - } -); - -// TODO validation includes 0 -> should be non-negative number? -export const positiveNumberRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError', - { - defaultMessage: 'Only positive numbers are allowed.', - } -); - -export const positiveNumbersAboveZeroErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError', - { - defaultMessage: 'Only numbers above 0 are allowed.', - } -); - -export const policyNameRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError', - { - defaultMessage: 'A policy name is required.', - } -); - -export const policyNameStartsWithUnderscoreErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError', - { - defaultMessage: 'A policy name cannot start with an underscore.', - } -); -export const policyNameContainsCommaErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError', - { - defaultMessage: 'A policy name cannot include a comma.', - } -); -export const policyNameContainsSpaceErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError', - { - defaultMessage: 'A policy name cannot include a space.', - } -); - -export const policyNameTooLongErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError', - { - defaultMessage: 'A policy name cannot be longer than 255 bytes.', - } -); -export const policyNameMustBeDifferentErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.differentPolicyNameRequiredError', - { - defaultMessage: 'The policy name must be different.', - } -); -export const policyNameAlreadyUsedErrorMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError', - { - defaultMessage: 'That policy name is already used.', - } -); -export type PhaseValidationErrors = { - [P in keyof Partial]: string[]; -}; - -export interface ValidationErrors { - delete: PhaseValidationErrors; - policyName: string[]; -} - -export const validatePolicy = ( - saveAsNew: boolean, - policy: LegacyPolicy, - policies: PolicyFromES[], - originalPolicyName: string -): [boolean, ValidationErrors] => { - const policyNameErrors: string[] = []; - if (!policy.name) { - policyNameErrors.push(policyNameRequiredMessage); - } else { - if (policy.name.startsWith('_')) { - policyNameErrors.push(policyNameStartsWithUnderscoreErrorMessage); - } - if (policy.name.includes(',')) { - policyNameErrors.push(policyNameContainsCommaErrorMessage); - } - if (policy.name.includes(' ')) { - policyNameErrors.push(policyNameContainsSpaceErrorMessage); - } - if (window.TextEncoder && new window.TextEncoder().encode(policy.name).length > 255) { - policyNameErrors.push(policyNameTooLongErrorMessage); - } - - if (saveAsNew && policy.name === originalPolicyName) { - policyNameErrors.push(policyNameMustBeDifferentErrorMessage); - } else if (policy.name !== originalPolicyName) { - const policyNames = policies.map((existingPolicy) => existingPolicy.name); - if (policyNames.includes(policy.name)) { - policyNameErrors.push(policyNameAlreadyUsedErrorMessage); - } - } - } - - const deletePhaseErrors = validateDeletePhase(policy.phases.delete); - const isValid = policyNameErrors.length === 0 && Object.keys(deletePhaseErrors).length === 0; - return [ - isValid, - { - policyName: [...policyNameErrors], - delete: deletePhaseErrors, - }, - ]; -}; - -export const findFirstError = (errors?: ValidationErrors): string | undefined => { - if (!errors) { - return; - } - - if (errors.policyName.length > 0) { - return propertyof('policyName'); - } - - if (Object.keys(errors.delete).length > 0) { - return `${propertyof('delete')}.${Object.keys(errors.delete)[0]}`; - } -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index 023aeba57aa7a..a127574d5bad0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -18,6 +18,7 @@ export { getFieldValidityAndErrorMessage, useFormContext, FormSchema, + ValidationConfig, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; @@ -27,6 +28,8 @@ export { NumericField, SelectField, SuperSelectField, + ComboBoxField, + TextField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e7784846598e4..baa4f37791007 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9119,24 +9119,18 @@ "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton": "再試行", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsLoadingFailedTitle": "ノード属性詳細を読み込めません", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsReloadButton": "再試行", - "xpack.indexLifecycleMgmt.editPolicy.numberRequiredError": "数字が必要です。", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel": "コールドフェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel": "コールドフェーズのタイミングの単位", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel": "削除フェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel": "削除フェーズのタイミングの単位", - "xpack.indexLifecycleMgmt.editPolicy.phaseErrorMessage": "エラーを修正してください", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel": "ウォームフェーズのタイミング", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel": "ウォームフェーズのタイミングの単位", "xpack.indexLifecycleMgmt.editPolicy.policiesLoading": "ポリシーを読み込み中…", "xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError": "このポリシー名は既に使用されています。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError": "ポリシー名にはコンマを使用できません。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError": "ポリシー名にはスペースを使用できません。", "xpack.indexLifecycleMgmt.editPolicy.policyNameLabel": "ポリシー名", "xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError": "ポリシー名が必要です。", "xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError": "ポリシー名の頭にアンダーラインを使用することはできません。", "xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError": "ポリシー名は 255 バイト未満である必要があります。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError": "0 よりも大きい数字のみ使用できます。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError": "プラスの数字のみ使用できます。", "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "ロールオーバーからの経過日数", "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "ロールオーバーからの経過時間数", "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "ロールオーバーからの経過時間(マイクロ秒)", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f3cd662bacba7..c4274524928fd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9128,24 +9128,18 @@ "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesReloadButton": "重试", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsLoadingFailedTitle": "无法加载节点属性详情", "xpack.indexLifecycleMgmt.editPolicy.nodeDetailsReloadButton": "重试", - "xpack.indexLifecycleMgmt.editPolicy.numberRequiredError": "数字必填。", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel": "冷阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel": "冷阶段计时单位", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel": "删除阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel": "删除阶段计时单位", - "xpack.indexLifecycleMgmt.editPolicy.phaseErrorMessage": "修复错误", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel": "温阶段计时", "xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel": "温阶段计时单位", "xpack.indexLifecycleMgmt.editPolicy.policiesLoading": "正在加载策略……", "xpack.indexLifecycleMgmt.editPolicy.policyNameAlreadyUsedError": "该策略名称已被使用。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsCommaError": "策略名称不能包含逗号。", - "xpack.indexLifecycleMgmt.editPolicy.policyNameContainsSpaceError": "策略名称不能包含空格。", "xpack.indexLifecycleMgmt.editPolicy.policyNameLabel": "策略名称", "xpack.indexLifecycleMgmt.editPolicy.policyNameRequiredError": "策略名称必填。", "xpack.indexLifecycleMgmt.editPolicy.policyNameStartsWithUnderscoreError": "策略名称不能以下划线开头。", "xpack.indexLifecycleMgmt.editPolicy.policyNameTooLongError": "策略名称的长度不能大于 255 字节。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberAboveZeroRequiredError": "仅允许使用 0 以上的数字。", - "xpack.indexLifecycleMgmt.editPolicy.positiveNumberRequiredError": "仅允许使用正数。", "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "天(自滚动更新)", "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "小时(自滚动更新)", "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "微秒(自滚动更新)", From c78cf35ba8bfdff5f61d3932cbacd45a69e4f773 Mon Sep 17 00:00:00 2001 From: Dhruv Bodani Date: Mon, 9 Nov 2020 19:05:05 +0530 Subject: [PATCH 09/87] Added `defaultActionMessage` to index threshold alert UI type definition (#80936) * resolves https://github.com/elastic/kibana/issues/78148 Adds a `defaultActionMessage` to the index threshold alert, so that the `message` parameter for actions will be pre-filled with a useful message --- .../index_threshold/action_context.test.ts | 3 ++ .../index_threshold/action_context.ts | 7 ++-- .../index_threshold/alert_type.test.ts | 4 ++ .../alert_types/index_threshold/alert_type.ts | 12 ++++++ .../builtin_alert_types/threshold/index.ts | 7 +++- .../index_threshold/alert.ts | 41 ++++++++++++++----- .../alert_create_flyout.ts | 5 ++- 7 files changed, 63 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts index 3f5addb77cb33..48847686828a9 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts @@ -25,6 +25,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 42, + function: 'count > 4', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( @@ -53,6 +54,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 42, + function: 'avg([aggField]) > 4.2', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( @@ -80,6 +82,7 @@ describe('ActionContext', () => { date: '2020-01-01T00:00:00.000Z', group: '[group]', value: 4, + function: 'count between 4,5', }; const context = addMessages({ name: '[alert-name]' }, base, params); expect(context.title).toMatchInlineSnapshot( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts index 5135e31e9322c..9bb0df9d07fd4 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts @@ -27,6 +27,8 @@ export interface BaseActionContext extends AlertInstanceContext { date: string; // the value that met the threshold value: number; + // the function that is used + function: string; } export function addMessages( @@ -42,9 +44,6 @@ export function addMessages( }, }); - const agg = params.aggField ? `${params.aggType}(${params.aggField})` : `${params.aggType}`; - const humanFn = `${agg} ${params.thresholdComparator} ${params.threshold.join(',')}`; - const window = `${params.timeWindowSize}${params.timeWindowUnit}`; const message = i18n.translate( 'xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription', @@ -55,7 +54,7 @@ export function addMessages( name: alertInfo.name, group: baseContext.group, value: baseContext.value, - function: humanFn, + function: baseContext.function, window, date: baseContext.date, }, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index 2f0cf3cbbcd16..d75f3af22ab06 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -46,6 +46,10 @@ describe('alertType', () => { "description": "The value that exceeded the threshold.", "name": "value", }, + Object { + "description": "A string describing the threshold comparator and threshold", + "name": "function", + }, ], "params": Array [ Object { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 2a1ed429b7fe1..e0a9cd981dac0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -83,6 +83,13 @@ export function getAlertType(service: Service): AlertType { return { @@ -107,6 +114,7 @@ export function getAlertType(service: Service): AlertType import('./expression')), validate: validateExpression, + defaultActionMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinAlertTypes.threshold.alertDefaultActionMessage', + { + defaultMessage: `alert \\{\\{alertName\\}\\} group \\{\\{context.group\\}\\} value \\{\\{context.value\\}\\} exceeded threshold \\{\\{context.function\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\} on \\{\\{context.date\\}\\}`, + } + ), requiresAppContext: false, }; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index 92db0458c0639..c05fa6cf051ff 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -15,6 +15,7 @@ import { ObjectRemover, } from '../../../../../common/lib'; import { createEsDocuments } from './create_test_data'; +import { getAlertType } from '../../../../../../../plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/'; const ALERT_TYPE_ID = '.index-threshold'; const ACTION_TYPE_ID = '.index'; @@ -26,6 +27,8 @@ const ALERT_INTERVALS_TO_WRITE = 5; const ALERT_INTERVAL_SECONDS = 3; const ALERT_INTERVAL_MILLIS = ALERT_INTERVAL_SECONDS * 1000; +const DefaultActionMessage = getAlertType().defaultActionMessage; + // eslint-disable-next-line import/no-default-export export default function alertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -62,6 +65,10 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); }); + it('has a default action message', () => { + expect(DefaultActionMessage).to.be.ok(); + }); + // The tests below create two alerts, one that will fire, one that will // never fire; the tests ensure the ones that should fire, do fire, and // those that shouldn't fire, do not fire. @@ -85,7 +92,7 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(2); for (const doc of docs) { const { group } = doc._source; - const { name, value, title, message } = doc._source.params; + const { name, title, message } = doc._source.params; expect(name).to.be('always fire'); expect(group).to.be('all documents'); @@ -93,9 +100,8 @@ export default function alertTests({ getService }: FtrProviderContext) { // we'll check title and message in this test, but not subsequent ones expect(title).to.be('alert always fire group all documents exceeded threshold'); - const expectedPrefix = `alert always fire group all documents value ${value} exceeded threshold count > -1 over`; - const messagePrefix = message.substr(0, expectedPrefix.length); - expect(messagePrefix).to.be(expectedPrefix); + const messagePattern = /alert always fire group all documents value \d+ exceeded threshold count > -1 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -128,10 +134,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-0') inGroup0++; + + const messagePattern = /alert always fire group group-\d value \d+ exceeded threshold count .+ over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-0, rando split between others @@ -163,9 +172,12 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(2); for (const doc of docs) { - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); + + const messagePattern = /alert always fire group all documents value \d+ exceeded threshold sum\(testedValue\) between 0,1000000 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -195,9 +207,12 @@ export default function alertTests({ getService }: FtrProviderContext) { const docs = await waitForDocs(4); for (const doc of docs) { - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); + + const messagePattern = /alert always fire group all documents value .+ exceeded threshold avg\(testedValue\) .+ 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } }); @@ -232,10 +247,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-2') inGroup2++; + + const messagePattern = /alert always fire group group-. value \d+ exceeded threshold max\(testedValue\) .* 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-2, rando split between others @@ -274,10 +292,13 @@ export default function alertTests({ getService }: FtrProviderContext) { for (const doc of docs) { const { group } = doc._source; - const { name } = doc._source.params; + const { name, message } = doc._source.params; expect(name).to.be('always fire'); if (group === 'group-0') inGroup0++; + + const messagePattern = /alert always fire group group-. value \d+ exceeded threshold min\(testedValue\) .* 0 over 15s on \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); } // there should be 2 docs in group-0, rando split between others @@ -329,7 +350,7 @@ export default function alertTests({ getService }: FtrProviderContext) { name: '{{{alertName}}}', value: '{{{context.value}}}', title: '{{{context.title}}}', - message: '{{{context.message}}}', + message: DefaultActionMessage, }, date: '{{{context.date}}}', // TODO: I wanted to write the alert value here, but how? diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index ee0de582a9bff..0f6da936f8644 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -79,10 +79,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); const createdConnectorToastTitle = await pageObjects.common.closeToast(); expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + const messageTextArea = await find.byCssSelector('[data-test-subj="messageTextArea"]'); + expect(await messageTextArea.getAttribute('value')).to.eql( + 'alert {{alertName}} group {{context.group}} value {{context.value}} exceeded threshold {{context.function}} over {{params.timeWindowSize}}{{params.timeWindowUnit}} on {{context.date}}' + ); await testSubjects.setValue('messageTextArea', 'test message '); await testSubjects.click('messageAddVariableButton'); await testSubjects.click('variableMenuButton-0'); - const messageTextArea = await find.byCssSelector('[data-test-subj="messageTextArea"]'); expect(await messageTextArea.getAttribute('value')).to.eql('test message {{alertId}}'); await messageTextArea.type(' some additional text '); From 0217073b8f555b68a7487c5c52325e462a5232af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 9 Nov 2020 11:03:07 -0300 Subject: [PATCH 10/87] [APM] Transition to Elastic charts for all relevant APM charts (#80298) * adding elastic charts * fixing some stuff * refactoring * fixing ts issues * fixing unit test * fix i18n * adding isLoading prop * adding annotations toggle, replacing transaction error rate to elastic chart * adding loading state * adding empty message * fixing i18n * removing unused files * fixing i18n * removing e2e test since elastic charts uses canvas * addressing pr comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apm/e2e/cypress/integration/apm.feature | 3 +- .../cypress/support/step_definitions/apm.ts | 13 - .../ErrorGroupDetails/Distribution/index.tsx | 141 +- .../TransactionDetails/Distribution/index.tsx | 198 ++- .../app/TransactionDetails/index.tsx | 9 +- .../TransactionOverview.test.tsx | 14 +- .../app/TransactionOverview/index.tsx | 23 +- .../components/app/service_overview/index.tsx | 16 +- .../TransactionBreakdownGraph/index.tsx | 143 +- .../shared/TransactionBreakdown/index.tsx | 8 +- .../shared/charts/Histogram/SingleRect.js | 29 - .../Histogram/__test__/Histogram.test.js | 119 -- .../__snapshots__/Histogram.test.js.snap | 1504 ----------------- .../charts/Histogram/__test__/response.json | 106 -- .../shared/charts/Histogram/index.js | 319 ---- .../TransactionLineChart/index.tsx | 70 - .../shared/charts/TransactionCharts/index.tsx | 133 +- .../TransactionCharts/use_formatter.test.tsx | 106 +- .../charts/TransactionCharts/use_formatter.ts | 38 +- .../shared/charts/annotations/index.tsx | 45 + .../shared/charts/chart_container.test.tsx | 91 +- .../shared/charts/chart_container.tsx | 41 +- .../legacy.tsx | 112 -- .../shared/charts/line_chart/index.tsx | 16 +- .../index.tsx | 56 +- .../public/hooks/useTransactionBreakdown.ts | 2 +- .../hooks/useTransactionDistribution.ts | 2 +- .../apm/public/hooks/useTransactionList.ts | 4 +- .../apm/public/hooks/use_annotations.ts | 38 + .../apm/public/selectors/chartSelectors.ts | 41 +- .../lib/errors/distribution/get_buckets.ts | 2 +- .../translations/translations/ja-JP.json | 9 - .../translations/translations/zh-CN.json | 9 - 33 files changed, 718 insertions(+), 2742 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx delete mode 100644 x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx rename x-pack/plugins/apm/public/components/shared/charts/{erroneous_transactions_rate_chart => transaction_error_rate_chart}/index.tsx (64%) create mode 100644 x-pack/plugins/apm/public/hooks/use_annotations.ts diff --git a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature index 285615108266b..494a6b5fadb5b 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature +++ b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature @@ -3,5 +3,4 @@ Feature: APM Scenario: Transaction duration charts Given a user browses the APM UI application When the user inspects the opbeans-node service - Then should redirect to correct path with correct params - And should have correct y-axis ticks + Then should redirect to correct path with correct params \ No newline at end of file diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index 50c620dca9ddf..42c2bc7ffd318 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -29,16 +29,3 @@ Then(`should redirect to correct path with correct params`, () => { cy.url().should('contain', `/app/apm/services/opbeans-node/transactions`); cy.url().should('contain', `transactionType=request`); }); - -Then(`should have correct y-axis ticks`, () => { - const yAxisTick = - '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text'; - - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - - // literal assertions because snapshot() doesn't retry - cy.get(yAxisTick).eq(2).should('have.text', '55 ms'); - cy.get(yAxisTick).eq(1).should('have.text', '28 ms'); - cy.get(yAxisTick).eq(0).should('have.text', '0 ms'); -}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index e17dd9a9eb038..a17bf7e93e466 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -4,31 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + Axis, + Chart, + HistogramBarSeries, + niceTimeFormatter, + Position, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, +} from '@elastic/charts'; import { EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import d3 from 'd3'; -import { scaleUtc } from 'd3-scale'; -import { mean } from 'lodash'; import React from 'react'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; -import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs'; -// @ts-expect-error -import Histogram from '../../../shared/charts/Histogram'; -import { EmptyMessage } from '../../../shared/EmptyMessage'; - -interface IBucket { - key: number; - count: number | undefined; -} - -// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse) -interface IDistribution { - noHits: boolean; - buckets: IBucket[]; - bucketSize: number; -} +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution'; +import { useTheme } from '../../../../hooks/useTheme'; interface FormattedBucket { x0: number; @@ -37,13 +30,9 @@ interface FormattedBucket { } export function getFormattedBuckets( - buckets: IBucket[], + buckets: ErrorDistributionAPIResponse['buckets'], bucketSize: number -): FormattedBucket[] | null { - if (!buckets) { - return null; - } - +): FormattedBucket[] { return buckets.map(({ count, key }) => { return { x0: key, @@ -54,76 +43,66 @@ export function getFormattedBuckets( } interface Props { - distribution: IDistribution; + distribution: ErrorDistributionAPIResponse; title: React.ReactNode; } -const tooltipHeader = (bucket: FormattedBucket) => - asRelativeDateTimeRange(bucket.x0, bucket.x); - export function ErrorDistribution({ distribution, title }: Props) { + const theme = useTheme(); const buckets = getFormattedBuckets( distribution.buckets, distribution.bucketSize ); - if (!buckets) { - return ( - - ); - } - - const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0; const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat(); + const xMax = d3.max(buckets, (d) => d.x0); + + const xFormatter = niceTimeFormatter([xMin, xMax]); + + const tooltipProps: SettingsSpec['tooltip'] = { + headerFormatter: (tooltip: TooltipValue) => { + const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); + if (serie) { + return asRelativeDateTimeRange(serie.x0, serie.x); + } + return `${tooltip.value}`; + }, + }; return (
{title} - bucket.x} - xType="time-utc" - formatX={(value: Date) => { - const time = value.getTime(); - return tickFormat(new Date(time - getTimezoneOffsetInMs(time))); - }} - buckets={buckets} - bucketSize={distribution.bucketSize} - formatYShort={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesShortLabel', { - defaultMessage: '{occCount} occ.', - values: { occCount: value }, - }) - } - formatYLong={(value: number) => - i18n.translate('xpack.apm.errorGroupDetails.occurrencesLongLabel', { - defaultMessage: - '{occCount} {occCount, plural, one {occurrence} other {occurrences}}', - values: { occCount: value }, - }) - } - legends={[ - { - color: theme.euiColorVis1, - // 0a abbreviates large whole numbers with metric prefixes like: 1000 = 1k, 32000 = 32k, 1000000 = 1m - legendValue: numeral(averageValue).format('0a'), - title: i18n.translate('xpack.apm.errorGroupDetails.avgLabel', { - defaultMessage: 'Avg.', - }), - legendClickDisabled: true, - }, - ]} - /> +
+ + + + + + +
); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 67125d41635a9..bf1bda793179f 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -4,22 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + Axis, + Chart, + ElementClickListener, + GeometryValue, + HistogramBarSeries, + Position, + RectAnnotation, + ScaleType, + Settings, + SettingsSpec, + TooltipValue, + XYChartSeriesIdentifier, +} from '@elastic/charts'; import { EuiIconTip, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import d3 from 'd3'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { ValuesType } from 'utility-types'; +import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; +import type { DistributionBucket } from '../../../../../server/lib/transactions/distribution/get_buckets'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -// @ts-expect-error -import Histogram from '../../../shared/charts/Histogram'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { unit } from '../../../../style/variables'; +import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; interface IChartPoint { x0: number; @@ -31,10 +46,10 @@ interface IChartPoint { } export function getFormattedBuckets( - buckets: DistributionBucket[], - bucketSize: number + buckets?: DistributionBucket[], + bucketSize?: number ) { - if (!buckets) { + if (!buckets || !bucketSize) { return []; } @@ -74,7 +89,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# request} one {# request} other {# requests}}', + '{transCount, plural, =0 {request} one {request} other {requests}}', values: { transCount: t, }, @@ -84,7 +99,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel', { defaultMessage: - '{transCount, plural, =0 {# transaction} one {# transaction} other {# transactions}}', + '{transCount, plural, =0 {transaction} one {transaction} other {transactions}}', values: { transCount: t, }, @@ -95,21 +110,21 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => { interface Props { distribution?: TransactionDistributionAPIResponse; urlParams: IUrlParams; - isLoading: boolean; + fetchStatus: FETCH_STATUS; bucketIndex: number; onBucketClick: ( bucket: ValuesType ) => void; } -export function TransactionDistribution(props: Props) { - const { - distribution, - urlParams: { transactionType }, - isLoading, - bucketIndex, - onBucketClick, - } = props; +export function TransactionDistribution({ + distribution, + urlParams: { transactionType }, + fetchStatus, + bucketIndex, + onBucketClick, +}: Props) { + const theme = useTheme(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ const formatYShort = useCallback(getFormatYShort(transactionType), [ @@ -122,12 +137,10 @@ export function TransactionDistribution(props: Props) { ]); // no data in response - if (!distribution || distribution.noHits) { - // only show loading state if there is no data - else show stale data until new data has loaded - if (isLoading) { - return ; - } - + if ( + (!distribution || distribution.noHits) && + fetchStatus !== FETCH_STATUS.LOADING + ) { return ( { - return bucket.key === chartPoint.x0; - }); - - return clickedBucket; - } - const buckets = getFormattedBuckets( - distribution.buckets, - distribution.bucketSize + distribution?.buckets, + distribution?.bucketSize ); - const xMax = d3.max(buckets, (d) => d.x) || 0; + const xMin = d3.min(buckets, (d) => d.x0) || 0; + const xMax = d3.max(buckets, (d) => d.x0) || 0; const timeFormatter = getDurationFormatter(xMax); + const tooltipProps: SettingsSpec['tooltip'] = { + headerFormatter: (tooltip: TooltipValue) => { + const serie = buckets.find((bucket) => bucket.x0 === tooltip.value); + if (serie) { + const xFormatted = timeFormatter(serie.x); + const x0Formatted = timeFormatter(serie.x0); + return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + } + return `${timeFormatter(tooltip.value)}`; + }, + }; + + const onBarClick: ElementClickListener = (elements) => { + const chartPoint = elements[0][0] as GeometryValue; + const clickedBucket = distribution?.buckets.find((bucket) => { + return bucket.key === chartPoint.x; + }); + if (clickedBucket) { + onBucketClick(clickedBucket); + } + }; + + const selectedBucket = buckets[bucketIndex]; + return (
@@ -181,42 +211,66 @@ export function TransactionDistribution(props: Props) { /> - - { - const clickedBucket = getBucketFromChartPoint(chartPoint); - - if (clickedBucket) { - onBucketClick(clickedBucket); - } - }} - formatX={(time: number) => timeFormatter(time).formatted} - formatYShort={formatYShort} - formatYLong={formatYLong} - verticalLineHover={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) - } - backgroundHover={(point: IChartPoint) => - !isEmpty(getBucketFromChartPoint(point)?.samples) - } - tooltipHeader={(point: IChartPoint) => { - const xFormatted = timeFormatter(point.x); - const x0Formatted = timeFormatter(point.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - tooltipFooter={(point: IChartPoint) => - isEmpty(getBucketFromChartPoint(point)?.samples) && - i18n.translate( - 'xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip', - { - defaultMessage: 'No sample available for this bucket', - } - ) - } - /> + + + + {selectedBucket && ( + + )} + timeFormatter(time).formatted} + /> + formatYShort(value)} + /> + value} + minBarHeight={2} + id="transactionDurationDistribution" + name={(series: XYChartSeriesIdentifier) => { + const bucketCount = series.splitAccessors.get( + series.yAccessor + ) as number; + return formatYLong(bucketCount); + }} + splitSeriesAccessors={['y']} + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x0" + yAccessors={['y']} + data={buckets} + color={theme.eui.euiColorVis1} + /> + +
); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index efdd7b1f34221..e4c36b028e55c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -52,7 +52,11 @@ export function TransactionDetails({ status: distributionStatus, } = useTransactionDistribution(urlParams); - const { data: transactionChartsData } = useTransactionCharts(); + const { + data: transactionChartsData, + status: transactionChartsStatus, + } = useTransactionCharts(); + const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( urlParams ); @@ -121,6 +125,7 @@ export function TransactionDetails({ @@ -131,7 +136,7 @@ export function TransactionDetails({ { diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx index b7d1b93600a73..c530a7e1489ad 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - fireEvent, - getByText, - queryByLabelText, - render, -} from '@testing-library/react'; +import { fireEvent, getByText, queryByLabelText } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -20,7 +15,10 @@ import { UrlParamsProvider } from '../../../context/UrlParamsContext'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import * as useFetcherHook from '../../../hooks/useFetcher'; import * as useServiceTransactionTypesHook from '../../../hooks/useServiceTransactionTypes'; -import { disableConsoleWarning } from '../../../utils/testHelpers'; +import { + disableConsoleWarning, + renderWithTheme, +} from '../../../utils/testHelpers'; import { fromQuery } from '../../shared/Links/url_helpers'; import { TransactionOverview } from './'; @@ -54,7 +52,7 @@ function setup({ jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); - return render( + return renderWithTheme( diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 5444d2d521f37..df9e673ed4847 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -22,7 +22,7 @@ import React, { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { LegacyChartsSyncContextProvider as ChartsSyncContextProvider } from '../../../context/charts_sync_context'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; @@ -33,11 +33,10 @@ import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; +import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; import { UserExperienceCallout } from './user_experience_callout'; -import { Correlations } from '../Correlations'; function getRedirectLocation({ urlParams, @@ -83,7 +82,10 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { }) ); - const { data: transactionCharts } = useTransactionCharts(); + const { + data: transactionCharts, + status: transactionChartsStatus, + } = useTransactionCharts(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); @@ -135,12 +137,11 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} - - - + @@ -190,7 +191,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 342152b572f1e..016ee3daf6b51 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; import { ChartsSyncContextProvider } from '../../../context/charts_sync_context'; -import { ErroneousTransactionsRateChart } from '../../shared/charts/erroneous_transactions_rate_chart'; +import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; @@ -125,19 +125,7 @@ export function ServiceOverview({ {!isRumAgentName(agentName) && ( - - -

- {i18n.translate( - 'xpack.apm.serviceOverview.errorRateChartTitle', - { - defaultMessage: 'Error rate', - } - )} -

-
- -
+
)} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index b908eb8da4d03..05cae589c19fc 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -4,62 +4,113 @@ * you may not use this file except in compliance with the Elastic License. */ -import { throttle } from 'lodash'; -import React, { useMemo } from 'react'; +import { + AreaSeries, + Axis, + Chart, + niceTimeFormatter, + Placement, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useUiTracker } from '../../../../../../observability/public'; -import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; -import { Maybe } from '../../../../../typings/common'; -import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { getEmptySeries } from '../../charts/CustomPlot/getEmptySeries'; -import { TransactionLineChart } from '../../charts/TransactionCharts/TransactionLineChart'; +import { useChartsSync as useChartsSync2 } from '../../../../hooks/use_charts_sync'; +import { unit } from '../../../../style/variables'; +import { Annotations } from '../../charts/annotations'; +import { ChartContainer } from '../../charts/chart_container'; +import { onBrushEnd } from '../../charts/helper/helper'; + +const XY_HEIGHT = unit * 16; interface Props { - timeseries: TimeSeries[]; - noHits: boolean; + fetchStatus: FETCH_STATUS; + timeseries?: TimeSeries[]; } -const tickFormatY = (y: Maybe) => { - return asPercent(y ?? 0, 1); -}; +export function TransactionBreakdownGraph({ fetchStatus, timeseries }: Props) { + const history = useHistory(); + const chartRef = React.createRef(); + const { event, setEvent } = useChartsSync2(); + const { urlParams } = useUrlParams(); + const { start, end } = urlParams; -const formatTooltipValue = (coordinate: Coordinate) => { - return isValidCoordinateValue(coordinate.y) - ? asPercent(coordinate.y, 1) - : NOT_AVAILABLE_LABEL; -}; + useEffect(() => { + if (event.chartId !== 'timeSpentBySpan' && chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(event); + } + }, [chartRef, event]); -function TransactionBreakdownGraph({ timeseries, noHits }: Props) { - const { urlParams } = useUrlParams(); - const { rangeFrom, rangeTo } = urlParams; - const trackApmEvent = useUiTracker({ app: 'apm' }); - const handleHover = useMemo( - () => - throttle(() => trackApmEvent({ metric: 'hover_breakdown_chart' }), 60000), - [trackApmEvent] - ); + const min = moment.utc(start).valueOf(); + const max = moment.utc(end).valueOf(); - const emptySeries = - rangeFrom && rangeTo - ? getEmptySeries( - new Date(rangeFrom).getTime(), - new Date(rangeTo).getTime() - ) - : []; + const xFormatter = niceTimeFormatter([min, max]); return ( - + + + onBrushEnd({ x, history })} + showLegend + showLegendExtra + legendPosition={Position.Bottom} + xDomain={{ min, max }} + flatLegend + onPointerUpdate={(currEvent: any) => { + setEvent(currEvent); + }} + externalPointerEvents={{ + tooltip: { visible: true, placement: Placement.Bottom }, + }} + /> + + asPercent(y ?? 0, 1)} + /> + + + + {timeseries?.length ? ( + timeseries.map((serie) => { + return ( + + ); + }) + ) : ( + // When timeseries is empty, loads an AreaSeries chart to show the default empty message. + + )} + + ); } - -export { TransactionBreakdownGraph }; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index 55826497ca385..9b0c041aaf7b5 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -5,16 +5,13 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; function TransactionBreakdown() { const { data, status } = useTransactionBreakdown(); const { timeseries } = data; - const noHits = isEmpty(timeseries) && status === FETCH_STATUS.SUCCESS; return ( @@ -29,7 +26,10 @@ function TransactionBreakdown() { - +
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js deleted file mode 100644 index ca85ee961f5d8..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/SingleRect.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -function SingleRect({ innerHeight, marginTop, style, x, width }) { - return ( - - ); -} - -SingleRect.requiresSVG = true; -SingleRect.propTypes = { - x: PropTypes.number.isRequired, -}; - -export default SingleRect; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js deleted file mode 100644 index 03fd039a3401e..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js +++ /dev/null @@ -1,119 +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 React from 'react'; - -import d3 from 'd3'; -import { HistogramInner } from '../index'; -import response from './response.json'; -import { - disableConsoleWarning, - toJson, - mountWithTheme, -} from '../../../../../utils/testHelpers'; -import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index'; -import { - asInteger, - getDurationFormatter, -} from '../../../../../../common/utils/formatters'; - -describe('Histogram', () => { - let mockConsole; - let wrapper; - - const onClick = jest.fn(); - - beforeAll(() => { - mockConsole = disableConsoleWarning('Warning: componentWillReceiveProps'); - }); - - afterAll(() => { - mockConsole.mockRestore(); - }); - - beforeEach(() => { - const buckets = getFormattedBuckets(response.buckets, response.bucketSize); - const xMax = d3.max(buckets, (d) => d.x); - const timeFormatter = getDurationFormatter(xMax); - - wrapper = mountWithTheme( - timeFormatter(time).formatted} - formatYShort={(t) => `${asInteger(t)} occ.`} - formatYLong={(t) => `${asInteger(t)} occurrences`} - tooltipHeader={(bucket) => { - const xFormatted = timeFormatter(bucket.x); - const x0Formatted = timeFormatter(bucket.x0); - return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; - }} - width={800} - /> - ); - }); - - describe('Initially', () => { - it('should have default markup', () => { - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - it('should not show tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over an empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(2).simulate('mouseOver'); - }); - - it('should not display tooltip', () => { - expect(wrapper.find('Tooltip').length).toBe(0); - }); - }); - - describe('when hovering over a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('mouseOver'); - }); - - it('should display tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - - expect(tooltips.length).toBe(1); - expect(tooltips.prop('header')).toBe('811 - 927 ms'); - expect(tooltips.prop('tooltipPoints')).toEqual([ - { value: '49 occurrences' }, - ]); - expect(tooltips.prop('x')).toEqual(869010); - expect(tooltips.prop('y')).toEqual(27.5); - }); - - it('should have correct markup for tooltip', () => { - const tooltips = wrapper.find('Tooltip'); - expect(toJson(tooltips)).toMatchSnapshot(); - }); - }); - - describe('when clicking on a non-empty bucket', () => { - beforeEach(() => { - wrapper.find('.rv-voronoi__cell').at(7).simulate('click'); - }); - - it('should call onClick with bucket', () => { - expect(onClick).toHaveBeenCalledWith({ - style: { cursor: 'pointer' }, - xCenter: 869010, - x0: 811076, - x: 926944, - y: 49, - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap deleted file mode 100644 index a31b9735628ab..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap +++ /dev/null @@ -1,1504 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Histogram Initially should have default markup 1`] = ` -.c0 { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - position: absolute; - top: 0; - left: 0; -} - -
- -
-
- - - - - - - - - - - - - 0 ms - - - - - - 500 ms - - - - - - 1,000 ms - - - - - - 1,500 ms - - - - - - 2,000 ms - - - - - - 2,500 ms - - - - - - 3,000 ms - - - - - - - - - - 0 occ. - - - - - - 28 occ. - - - - - - 55 occ. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-`; - -exports[`Histogram when hovering over a non-empty bucket should have correct markup for tooltip 1`] = ` -.c0 { - margin: 0 16px; - -webkit-transform: translateY(-50%); - -ms-transform: translateY(-50%); - transform: translateY(-50%); - border: 1px solid #d3dae6; - background: #ffffff; - border-radius: 4px; - font-size: 14px; - color: #000000; -} - -.c1 { - background: #f5f7fa; - border-bottom: 1px solid #d3dae6; - border-radius: 4px 4px 0 0; - padding: 8px; - color: #98a2b3; -} - -.c2 { - margin: 8px; - margin-right: 16px; - font-size: 12px; -} - -.c4 { - color: #98a2b3; - margin: 8px; - font-size: 12px; -} - -.c3 { - color: #69707d; - font-size: 14px; -} - -
- -
- -
- 811 - 927 ms -
-
- -
- -
- 49 occurrences -
-
-
-
- -
- -
-
-
-`; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json deleted file mode 100644 index 302e105dfa997..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/response.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "buckets": [ - { "key": 0, "count": 0 }, - { "key": 115868, "count": 0 }, - { "key": 231736, "count": 0 }, - { "key": 347604, "count": 0 }, - { "key": 463472, "count": 0 }, - { - "key": 579340, - "count": 8, - "samples": [ - { - "transactionId": "99437ee4-08d4-41f5-9b2b-93cc32ec3dfb" - } - ] - }, - { - "key": 695208, - "count": 23, - "samples": [ - { - "transactionId": "d327611b-e999-4942-a94f-c60208940180" - } - ] - }, - { - "key": 811076, - "count": 49, - "samples": [ - { - "transactionId": "99c50a5b-44b4-4289-a3d1-a2815d128192" - } - ] - }, - { - "key": 926944, - "count": 51, - "transactionId": "9706a1ec-23f5-4ce8-97e8-69ce35fb0a9a" - }, - { - "key": 1042812, - "count": 46, - "transactionId": "f8d360c3-dd5e-47b6-b082-9e0bf821d3b2" - }, - { - "key": 1158680, - "count": 13, - "samples": [ - { - "transactionId": "8486d3e2-7f15-48df-aa37-6ee9955adbd2" - } - ] - }, - { - "key": 1274548, - "count": 7, - "transactionId": "54b4b5a7-f065-4cab-9016-534e58f4fc0a" - }, - { - "key": 1390416, - "count": 4, - "transactionId": "8cfac2a3-38e7-4d3a-9792-d008b4bcb867" - }, - { - "key": 1506284, - "count": 3, - "transactionId": "ce3f3bd3-a37c-419e-bb9c-5db956ded149" - }, - { "key": 1622152, "count": 0 }, - { - "key": 1738020, - "count": 4, - "transactionId": "2300174b-85d8-40ba-a6cb-eeba2a49debf" - }, - { "key": 1853888, "count": 0 }, - { "key": 1969756, "count": 0 }, - { - "key": 2085624, - "count": 1, - "transactionId": "774955a4-2ba3-4461-81a6-65759db4805d" - }, - { "key": 2201492, "count": 0 }, - { "key": 2317360, "count": 0 }, - { "key": 2433228, "count": 0 }, - { "key": 2549096, "count": 0 }, - { "key": 2664964, "count": 0 }, - { - "key": 2780832, - "count": 1, - "transactionId": "035d1b9d-af71-46cf-8910-57bd4faf412d" - }, - { - "key": 2896700, - "count": 1, - "transactionId": "4a845b32-9de4-4796-8ef4-d7bbdedc9099" - }, - { "key": 3012568, "count": 0 }, - { - "key": 3128436, - "count": 1, - "transactionId": "68620ffb-7a1b-4f8e-b9bb-009fa5b092be" - } - ], - "bucketSize": 115868, - "defaultBucketIndex": 12 -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js deleted file mode 100644 index 3b2109d68c613..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js +++ /dev/null @@ -1,319 +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 React, { PureComponent } from 'react'; -import d3 from 'd3'; -import { isEmpty } from 'lodash'; -import PropTypes from 'prop-types'; -import { scaleLinear } from 'd3-scale'; -import styled from 'styled-components'; -import SingleRect from './SingleRect'; -import { - XYPlot, - XAxis, - YAxis, - HorizontalGridLines, - VerticalRectSeries, - Voronoi, - makeWidthFlexible, - VerticalGridLines, -} from 'react-vis'; -import { unit } from '../../../../style/variables'; -import Tooltip from '../Tooltip'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { tint } from 'polished'; -import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone'; -import Legends from '../CustomPlot/Legends'; -import StatusText from '../CustomPlot/StatusText'; -import { i18n } from '@kbn/i18n'; -import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; - -const XY_HEIGHT = unit * 10; -const XY_MARGIN = { - top: unit, - left: unit * 5, - right: unit, - bottom: unit * 2, -}; - -const X_TICK_TOTAL = 8; - -// position absolutely to make sure that window resizing/zooming works -const ChartsWrapper = styled.div` - user-select: none; - position: absolute; - top: 0; - left: 0; -`; - -export class HistogramInner extends PureComponent { - constructor(props) { - super(props); - this.state = { - hoveredBucket: {}, - }; - } - - onClick = (bucket) => { - if (this.props.onClick) { - this.props.onClick(bucket); - } - }; - - onHover = (bucket) => { - this.setState({ hoveredBucket: bucket }); - }; - - onBlur = () => { - this.setState({ hoveredBucket: {} }); - }; - - getChartData(items, selectedItem) { - const yMax = d3.max(items, (d) => d.y); - const MINIMUM_BUCKET_SIZE = yMax * 0.02; - - return items.map((item) => { - const padding = (item.x - item.x0) / 20; - return { - ...item, - color: - item === selectedItem - ? theme.euiColorVis1 - : tint(0.5, theme.euiColorVis1), - x0: item.x0 + padding, - x: item.x - padding, - y: item.y > 0 ? Math.max(item.y, MINIMUM_BUCKET_SIZE) : 0, - }; - }); - } - - render() { - const { - backgroundHover, - bucketIndex, - buckets, - bucketSize, - formatX, - formatYShort, - formatYLong, - tooltipFooter, - tooltipHeader, - verticalLineHover, - width: XY_WIDTH, - height, - legends, - } = this.props; - const { hoveredBucket } = this.state; - if (isEmpty(buckets) || XY_WIDTH === 0) { - return null; - } - - const isTimeSeries = - this.props.xType === 'time' || this.props.xType === 'time-utc'; - - const xMin = d3.min(buckets, (d) => d.x0); - const xMax = d3.max(buckets, (d) => d.x); - const yMin = 0; - const yMax = d3.max(buckets, (d) => d.y); - const selectedBucket = buckets[bucketIndex]; - const chartData = this.getChartData(buckets, selectedBucket); - - const x = scaleLinear() - .domain([xMin, xMax]) - .range([XY_MARGIN.left, XY_WIDTH - XY_MARGIN.right]); - - const y = scaleLinear().domain([yMin, yMax]).range([XY_HEIGHT, 0]).nice(); - - const [xMinZone, xMaxZone] = getDomainTZ(xMin, xMax); - const xTickValues = isTimeSeries - ? getTimeTicksTZ({ - domain: [xMinZone, xMaxZone], - totalTicks: X_TICK_TOTAL, - width: XY_WIDTH, - }) - : undefined; - - const xDomain = x.domain(); - const yDomain = y.domain(); - const yTickValues = [0, yDomain[1] / 2, yDomain[1]]; - const shouldShowTooltip = - hoveredBucket.x > 0 && (hoveredBucket.y > 0 || isTimeSeries); - - const showVerticalLineHover = verticalLineHover(hoveredBucket); - const showBackgroundHover = backgroundHover(hoveredBucket); - - const hasValidCoordinates = buckets.some((bucket) => - isValidCoordinateValue(bucket.y) - ); - const noHits = this.props.noHits || !hasValidCoordinates; - - const xyPlotProps = { - dontCheckIfEmpty: true, - xType: this.props.xType, - width: XY_WIDTH, - height: XY_HEIGHT, - margin: XY_MARGIN, - xDomain: xDomain, - yDomain: yDomain, - }; - - const xAxisProps = { - style: { strokeWidth: '1px' }, - marginRight: 10, - tickSize: 0, - tickTotal: X_TICK_TOTAL, - tickFormat: formatX, - tickValues: xTickValues, - }; - - const emptyStateChart = ( - - - - - ); - - return ( -
- - {noHits ? ( - <>{emptyStateChart} - ) : ( - <> - - - - - - {showBackgroundHover && ( - - )} - - {shouldShowTooltip && ( - - )} - - {selectedBucket && ( - - )} - - - - {showVerticalLineHover && hoveredBucket?.x && ( - - )} - - { - return { - ...bucket, - xCenter: (bucket.x0 + bucket.x) / 2, - }; - })} - onClick={this.onClick} - onHover={this.onHover} - onBlur={this.onBlur} - x={(d) => x(d.xCenter)} - y={() => 1} - /> - - - {legends && ( - {}} - truncateLegends={false} - noHits={noHits} - /> - )} - - )} - -
- ); - } -} - -HistogramInner.propTypes = { - backgroundHover: PropTypes.func, - bucketIndex: PropTypes.number, - buckets: PropTypes.array.isRequired, - bucketSize: PropTypes.number.isRequired, - formatX: PropTypes.func, - formatYLong: PropTypes.func, - formatYShort: PropTypes.func, - onClick: PropTypes.func, - tooltipFooter: PropTypes.func, - tooltipHeader: PropTypes.func, - verticalLineHover: PropTypes.func, - width: PropTypes.number.isRequired, - height: PropTypes.number, - xType: PropTypes.string, - legends: PropTypes.array, - noHits: PropTypes.bool, -}; - -HistogramInner.defaultProps = { - backgroundHover: () => null, - formatYLong: (value) => value, - formatYShort: (value) => value, - tooltipFooter: () => null, - tooltipHeader: () => null, - verticalLineHover: () => null, - xType: 'linear', - noHits: false, - height: XY_HEIGHT, -}; - -export default makeWidthFlexible(HistogramInner); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx deleted file mode 100644 index 2e4b51af00d6b..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/TransactionLineChart/index.tsx +++ /dev/null @@ -1,70 +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 React, { useCallback } from 'react'; -import { Coordinate, TimeSeries } from '../../../../../../typings/timeseries'; -import { useLegacyChartsSync as useChartsSync } from '../../../../../hooks/use_charts_sync'; -// @ts-expect-error -import CustomPlot from '../../CustomPlot'; - -interface Props { - series: TimeSeries[]; - truncateLegends?: boolean; - tickFormatY: (y: number) => React.ReactNode; - formatTooltipValue: (c: Coordinate) => React.ReactNode; - yMax?: string | number; - height?: number; - stacked?: boolean; - onHover?: () => void; - visibleLegendCount?: number; - onToggleLegend?: (disabledSeriesState: boolean[]) => void; -} - -function TransactionLineChart(props: Props) { - const { - series, - tickFormatY, - formatTooltipValue, - yMax = 'max', - height, - truncateLegends, - stacked = false, - onHover, - visibleLegendCount, - onToggleLegend, - } = props; - - const syncedChartsProps = useChartsSync(); - - // combine callback for syncedChartsProps.onHover and props.onHover - const combinedOnHover = useCallback( - (hoverX: number) => { - if (onHover) { - onHover(); - } - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps, onHover] - ); - - return ( - - ); -} - -export { TransactionLineChart }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index b3c0c3b6de857..2a5948d0ebf0b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -20,104 +20,107 @@ import { TRANSACTION_REQUEST, TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; +import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { Coordinate } from '../../../../../typings/timeseries'; +import { ChartsSyncContextProvider } from '../../../../context/charts_sync_context'; import { LicenseContext } from '../../../../context/LicenseContext'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; -import { asDecimal, tpmUnit } from '../../../../../common/utils/formatters'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { ErroneousTransactionsRateChart } from '../erroneous_transactions_rate_chart/legacy'; import { TransactionBreakdown } from '../../TransactionBreakdown'; -import { - getResponseTimeTickFormatter, - getResponseTimeTooltipFormatter, -} from './helper'; +import { LineChart } from '../line_chart'; +import { TransactionErrorRateChart } from '../transaction_error_rate_chart/'; +import { getResponseTimeTickFormatter } from './helper'; import { MLHeader } from './ml_header'; -import { TransactionLineChart } from './TransactionLineChart'; import { useFormatter } from './use_formatter'; interface TransactionChartProps { charts: ITransactionChartData; urlParams: IUrlParams; + fetchStatus: FETCH_STATUS; } export function TransactionCharts({ charts, urlParams, + fetchStatus, }: TransactionChartProps) { const getTPMFormatter = (t: number) => { - const unit = tpmUnit(urlParams.transactionType); - return `${asDecimal(t)} ${unit}`; + return `${asDecimal(t)} ${tpmUnit(urlParams.transactionType)}`; }; - const getTPMTooltipFormatter = (p: Coordinate) => { - return isValidCoordinateValue(p.y) - ? getTPMFormatter(p.y) - : NOT_AVAILABLE_LABEL; + const getTPMTooltipFormatter = (y: Coordinate['y']) => { + return isValidCoordinateValue(y) ? getTPMFormatter(y) : NOT_AVAILABLE_LABEL; }; const { transactionType } = urlParams; const { responseTimeSeries, tpmSeries } = charts; - const { formatter, setDisabledSeriesState } = useFormatter( - responseTimeSeries - ); + const { formatter, toggleSerie } = useFormatter(responseTimeSeries); return ( <> - - - - - - - {responseTimeLabel(transactionType)} - - - - {(license) => ( - - )} - - - - - + + + + + + + + {responseTimeLabel(transactionType)} + + + + {(license) => ( + + )} + + + { + if (serie) { + toggleSerie(serie); + } + }} + /> + + - - - - {tpmLabel(transactionType)} - - - - - + + + + {tpmLabel(transactionType)} + + + + + - + - - - - - - - - + + + + + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx index fc873cbda7bf2..958a5db6b66c9 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.test.tsx @@ -3,38 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import { SeriesIdentifier } from '@elastic/charts'; +import { renderHook } from '@testing-library/react-hooks'; +import { act } from 'react-test-renderer'; +import { toMicroseconds } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; import { useFormatter } from './use_formatter'; -import { render, fireEvent, act } from '@testing-library/react'; -import { toMicroseconds } from '../../../../../common/utils/formatters'; - -function MockComponent({ - timeSeries, - disabledSeries, - value, -}: { - timeSeries: TimeSeries[]; - disabledSeries: boolean[]; - value: number; -}) { - const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - - const onDisableSeries = () => { - setDisabledSeriesState(disabledSeries); - }; - - return ( -
- - {formatter(value).formatted} -
- ); -} describe('useFormatter', () => { const timeSeries = ([ { + title: 'avg', data: [ { x: 1, y: toMicroseconds(11, 'minutes') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -42,6 +21,7 @@ describe('useFormatter', () => { ], }, { + title: '95th percentile', data: [ { x: 1, y: toMicroseconds(120, 'seconds') }, { x: 2, y: toMicroseconds(1, 'minutes') }, @@ -49,6 +29,7 @@ describe('useFormatter', () => { ], }, { + title: '99th percentile', data: [ { x: 1, y: toMicroseconds(60, 'seconds') }, { x: 2, y: toMicroseconds(5, 'minutes') }, @@ -56,54 +37,47 @@ describe('useFormatter', () => { ], }, ] as unknown) as TimeSeries[]; + it('returns new formatter when disabled series state changes', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('120 s')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); }); + it('falls back to the first formatter when disabled series is empty', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + const { result } = renderHook(() => useFormatter(timeSeries)); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - }); - it('falls back to the first formatter when disabled series is all true', () => { - const { getByText } = render( - - ); - expect(getByText('2.0 min')).toBeInTheDocument(); + + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('120 s'); + act(() => { - fireEvent.click(getByText('disable series')); + result.current.toggleSerie({ + specId: 'avg', + } as SeriesIdentifier); }); - expect(getByText('2.0 min')).toBeInTheDocument(); - // const { formatter, setDisabledSeriesState } = useFormatter(timeSeries); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); - // setDisabledSeriesState([true, true, false]); - // expect(formatter(toMicroseconds(120, 'seconds'))).toEqual('2.0 min'); + expect( + result.current.formatter(toMicroseconds(120, 'seconds')).formatted + ).toEqual('2.0 min'); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts index d4694bc3caf1d..1475ec2934e95 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/use_formatter.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, Dispatch, SetStateAction } from 'react'; -import { isEmpty } from 'lodash'; +import { SeriesIdentifier } from '@elastic/charts'; +import { omit } from 'lodash'; +import { useState } from 'react'; import { getDurationFormatter, TimeFormatter, @@ -14,17 +15,36 @@ import { TimeSeries } from '../../../../../typings/timeseries'; import { getMaxY } from './helper'; export const useFormatter = ( - series: TimeSeries[] + series?: TimeSeries[] ): { formatter: TimeFormatter; - setDisabledSeriesState: Dispatch>; + toggleSerie: (disabledSerie: SeriesIdentifier) => void; } => { - const [disabledSeriesState, setDisabledSeriesState] = useState([]); - const visibleSeries = series.filter( - (serie, index) => disabledSeriesState[index] !== true + const [disabledSeries, setDisabledSeries] = useState< + Record + >({}); + + const visibleSeries = series?.filter( + (serie) => disabledSeries[serie.title] === undefined ); - const maxY = getMaxY(isEmpty(visibleSeries) ? series : visibleSeries); + + const maxY = getMaxY(visibleSeries || series || []); const formatter = getDurationFormatter(maxY); - return { formatter, setDisabledSeriesState }; + const toggleSerie = ({ specId }: SeriesIdentifier) => { + if (disabledSeries[specId] !== undefined) { + setDisabledSeries((prevState) => { + return omit(prevState, specId); + }); + } else { + setDisabledSeries((prevState) => { + return { ...prevState, [specId]: 0 }; + }); + } + }; + + return { + formatter, + toggleSerie, + }; }; diff --git a/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx new file mode 100644 index 0000000000000..683c66b2a96fe --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/annotations/index.tsx @@ -0,0 +1,45 @@ +/* + * 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 { + AnnotationDomainTypes, + LineAnnotation, + Position, +} from '@elastic/charts'; +import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { asAbsoluteDateTime } from '../../../../../common/utils/formatters'; +import { useTheme } from '../../../../hooks/useTheme'; +import { useAnnotations } from '../../../../hooks/use_annotations'; + +export function Annotations() { + const { annotations } = useAnnotations(); + const theme = useTheme(); + + if (!annotations.length) { + return null; + } + + const color = theme.eui.euiColorSecondary; + + return ( + ({ + dataValue: annotation['@timestamp'], + header: asAbsoluteDateTime(annotation['@timestamp']), + details: `${i18n.translate('xpack.apm.chart.annotation.version', { + defaultMessage: 'Version', + })} ${annotation.text}`, + }))} + style={{ line: { strokeWidth: 1, stroke: color, opacity: 1 } }} + marker={} + markerPosition={Position.Top} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx index 409cb69575ca9..c0e8f869ce647 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -5,30 +5,97 @@ */ import { render } from '@testing-library/react'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; import { ChartContainer } from './chart_container'; describe('ChartContainer', () => { - describe('when isLoading is true', () => { - it('shows loading the indicator', () => { - const component = render( - + describe('loading indicator', () => { + it('shows loading when status equals to Loading or Pending and has no data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByTestId } = render( + +
My amazing component
+
+ ); + + expect(queryAllByTestId('loading')[0]).toBeInTheDocument(); + }); + }); + it('does not show loading when status equals to Loading or Pending and has data', () => { + [FETCH_STATUS.PENDING, FETCH_STATUS.LOADING].map((status) => { + const { queryAllByText } = render( + +
My amazing component
+
+ ); + expect(queryAllByText('My amazing component')[0]).toBeInTheDocument(); + }); + }); + }); + + describe('failure indicator', () => { + it('shows failure message when status equals to Failure and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.getByTestId('loading')).toBeInTheDocument(); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); + }); + it('shows failure message when status equals to Failure and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect( + getByText( + 'An error happened when trying to fetch data. Please try again' + ) + ).toBeInTheDocument(); }); }); - describe('when isLoading is false', () => { - it('does not show the loading indicator', () => { - const component = render( - + describe('render component', () => { + it('shows children component when status Success and has data', () => { + const { getByText } = render( +
My amazing component
); - - expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(getByText('My amazing component')).toBeInTheDocument(); + }); + it('shows children component when status Success and has no data', () => { + const { getByText } = render( + +
My amazing component
+
+ ); + expect(getByText('My amazing component')).toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index a6f579308597f..b4486f1e9b94a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -3,27 +3,56 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingChart } from '@elastic/eui'; + +import { EuiLoadingChart, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; interface Props { - isLoading: boolean; + hasData: boolean; + status: FETCH_STATUS; height: number; children: React.ReactNode; } -export function ChartContainer({ isLoading, children, height }: Props) { +export function ChartContainer({ children, height, status, hasData }: Props) { + if ( + !hasData && + (status === FETCH_STATUS.LOADING || status === FETCH_STATUS.PENDING) + ) { + return ; + } + + if (status === FETCH_STATUS.FAILURE) { + return ; + } + + return
{children}
; +} + +function LoadingChartPlaceholder({ height }: { height: number }) { return (
- {isLoading && } - {children} +
); } + +function FailedChartPlaceholder({ height }: { height: number }) { + return ( + + {i18n.translate('xpack.apm.chart.error', { + defaultMessage: + 'An error happened when trying to fetch data. Please try again', + })} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx b/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx deleted file mode 100644 index 29102f606414f..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/erroneous_transactions_rate_chart/legacy.tsx +++ /dev/null @@ -1,112 +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 { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { i18n } from '@kbn/i18n'; -import { max } from 'lodash'; -import React, { useCallback } from 'react'; -import { useParams } from 'react-router-dom'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { useLegacyChartsSync as useChartsSync } from '../../../../hooks/use_charts_sync'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; -// @ts-expect-error -import CustomPlot from '../CustomPlot'; - -const tickFormatY = (y?: number | null) => { - return asPercent(y || 0, 1); -}; - -/** - * "Legacy" version of this chart using react-vis charts. See index.tsx for the - * Elastic Charts version. - * - * This will be removed with #70290. - */ -export function ErroneousTransactionsRateChart() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { urlParams, uiFilters } = useUrlParams(); - const syncedChartsProps = useChartsSync(); - - const { start, end, transactionType, transactionName } = urlParams; - - const { data } = useFetcher(() => { - if (serviceName && start && end) { - return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/error_rate', - params: { - path: { - serviceName, - }, - query: { - start, - end, - transactionType, - transactionName, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters, transactionType, transactionName]); - - const combinedOnHover = useCallback( - (hoverX: number) => { - return syncedChartsProps.onHover(hoverX); - }, - [syncedChartsProps] - ); - - const errorRates = data?.transactionErrorRate || []; - const maxRate = max(errorRates.map((errorRate) => errorRate.y)); - - return ( - - - - {i18n.translate('xpack.apm.errorRateChart.title', { - defaultMessage: 'Transaction error rate', - })} - - - - - Number.isFinite(y) ? tickFormatY(y) : 'N/A' - } - /> - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx index 3f2a08ecb7641..507acc49d89db 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/line_chart/index.tsx @@ -20,15 +20,17 @@ import moment from 'moment'; import React, { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { TimeSeries } from '../../../../../typings/timeseries'; +import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useChartsSync } from '../../../../hooks/use_charts_sync'; import { unit } from '../../../../style/variables'; +import { Annotations } from '../annotations'; import { ChartContainer } from '../chart_container'; import { onBrushEnd } from '../helper/helper'; interface Props { id: string; - isLoading: boolean; + fetchStatus: FETCH_STATUS; onToggleLegend?: LegendItemListener; timeseries: TimeSeries[]; /** @@ -38,18 +40,20 @@ interface Props { /** * Formatter for legend and tooltip values */ - yTickFormat: (y: number) => string; + yTickFormat?: (y: number) => string; + showAnnotations?: boolean; } const XY_HEIGHT = unit * 16; export function LineChart({ id, - isLoading, + fetchStatus, onToggleLegend, timeseries, yLabelFormat, yTickFormat, + showAnnotations = true, }: Props) { const history = useHistory(); const chartRef = React.createRef(); @@ -84,7 +88,7 @@ export function LineChart({ ); return ( - + onBrushEnd({ x, history })} @@ -115,11 +119,13 @@ export function LineChart({ id="y-axis" ticks={3} position={Position.Left} - tickFormat={yTickFormat} + tickFormat={yTickFormat ? yTickFormat : yLabelFormat} labelFormat={yLabelFormat} showGridLines /> + {showAnnotations && } + {timeseries.map((serie) => { return ( (); const { urlParams, uiFilters } = useUrlParams(); @@ -56,25 +61,32 @@ export function ErroneousTransactionsRateChart() { const errorRates = data?.transactionErrorRate || []; return ( - + + +

+ {i18n.translate('xpack.apm.errorRate', { + defaultMessage: 'Error rate', + })} +

+
+ +
); } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts index 08d2300c3254a..0705383ecb0ca 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts @@ -15,7 +15,7 @@ export function useTransactionBreakdown() { uiFilters, } = useUrlParams(); - const { data = { timeseries: [] }, error, status } = useFetcher( + const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts index a5096a314388c..8c76225d03486 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts @@ -10,7 +10,7 @@ import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; import { useUiFilters } from '../context/UrlParamsContext'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; +import type { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index 9c3a18b9c0d0d..b2c2cc30f78ec 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -14,8 +14,8 @@ type TransactionsAPIResponse = APIReturnType< '/api/apm/services/{serviceName}/transaction_groups' >; -const DEFAULT_RESPONSE: TransactionsAPIResponse = { - items: [], +const DEFAULT_RESPONSE: Partial = { + items: undefined, isAggregationAccurate: true, bucketSize: 0, }; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/hooks/use_annotations.ts new file mode 100644 index 0000000000000..2b1c2bec52b3d --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_annotations.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { useParams } from 'react-router-dom'; +import { callApmApi } from '../services/rest/createCallApmApi'; +import { useFetcher } from './useFetcher'; +import { useUrlParams } from './useUrlParams'; + +const INITIAL_STATE = { annotations: [] }; + +export function useAnnotations() { + const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { environment } = uiFilters; + + const { data = INITIAL_STATE } = useFetcher(() => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment, + }, + }, + }); + } + }, [start, end, environment, serviceName]); + + return data; +} diff --git a/x-pack/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/plugins/apm/public/selectors/chartSelectors.ts index 8c6093859f969..450f02f70c6a4 100644 --- a/x-pack/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/plugins/apm/public/selectors/chartSelectors.ts @@ -31,40 +31,37 @@ export interface ITpmBucket { } export interface ITransactionChartData { - tpmSeries: ITpmBucket[]; - responseTimeSeries: TimeSeries[]; + tpmSeries?: ITpmBucket[]; + responseTimeSeries?: TimeSeries[]; mlJobId: string | undefined; } -const INITIAL_DATA = { - apmTimeseries: { - responseTimes: { - avg: [], - p95: [], - p99: [], - }, - tpmBuckets: [], - overallAvgDuration: null, - }, +const INITIAL_DATA: Partial = { + apmTimeseries: undefined, anomalyTimeseries: undefined, }; export function getTransactionCharts( { transactionType }: IUrlParams, - { apmTimeseries, anomalyTimeseries }: TimeSeriesAPIResponse = INITIAL_DATA + charts = INITIAL_DATA ): ITransactionChartData { - const tpmSeries = getTpmSeries(apmTimeseries, transactionType); - - const responseTimeSeries = getResponseTimeSeries({ - apmTimeseries, - anomalyTimeseries, - }); + const { apmTimeseries, anomalyTimeseries } = charts; - return { - tpmSeries, - responseTimeSeries, + const transactionCharts: ITransactionChartData = { + tpmSeries: undefined, + responseTimeSeries: undefined, mlJobId: anomalyTimeseries?.jobId, }; + + if (apmTimeseries) { + transactionCharts.tpmSeries = getTpmSeries(apmTimeseries, transactionType); + + transactionCharts.responseTimeSeries = getResponseTimeSeries({ + apmTimeseries, + anomalyTimeseries, + }); + } + return transactionCharts; } export function getResponseTimeSeries({ diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index a42710947a792..b12dd73a20986 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -73,6 +73,6 @@ export async function getBuckets({ return { noHits: resp.hits.total.value === 0, - buckets, + buckets: resp.hits.total.value > 0 ? buckets : [], }; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index baa4f37791007..485b24dced346 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4875,22 +4875,15 @@ "xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(", "xpack.apm.errorCountAlert.name": "エラー数しきい値", "xpack.apm.errorCountAlertTrigger.errors": " エラー", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "エラーグループ {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "エラーのオカレンス", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "例外メッセージ", "xpack.apm.errorGroupDetails.logMessageLabel": "ログメッセージ", - "xpack.apm.errorGroupDetails.noErrorsLabel": "エラーが見つかりませんでした", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "オカレンス", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} {occCount, plural, one {件の発生} other {件の発生}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 件", "xpack.apm.errorGroupDetails.relatedTransactionSample": "関連トランザクションサンプル", "xpack.apm.errorGroupDetails.unhandledLabel": "未対応", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "ディスカバリで {occurrencesCount} 件の{occurrencesCount, plural, one {ドキュメント} other {ドキュメント}}を表示。", - "xpack.apm.errorRateChart.avgLabel": "平均", - "xpack.apm.errorRateChart.rateLabel": "レート", - "xpack.apm.errorRateChart.title": "トランザクションエラー率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因", "xpack.apm.errorsTable.groupIdColumnDescription": "スタックトレースのハッシュ。動的パラメータのため、エラーメッセージが異なる場合でも、類似したエラーをグループ化します。", "xpack.apm.errorsTable.groupIdColumnLabel": "グループ ID", @@ -4917,7 +4910,6 @@ "xpack.apm.header.badge.readOnly.text": "読み込み専用", "xpack.apm.header.badge.readOnly.tooltip": "を保存できませんでした", "xpack.apm.helpMenu.upgradeAssistantLink": "アップグレードアシスタント", - "xpack.apm.histogram.plot.noDataLabel": "この時間範囲のデータがありません。", "xpack.apm.home.alertsMenu.alerts": "アラート", "xpack.apm.home.alertsMenu.createAnomalyAlert": "異常アラートを作成", "xpack.apm.home.alertsMenu.createThresholdAlert": "しきい値アラートを作成", @@ -5256,7 +5248,6 @@ "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionDetails.transactionLabel": "トランザクション", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "このバケットに利用可能なサンプルがありません", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# request} 1 {# 件のリクエスト} other {# 件のリクエスト}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# transaction} 1 {# 件のトランザクション} other {# 件のトランザクション}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} {transType, select, request {件のリクエスト} other {件のトランザクション}}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c4274524928fd..98d13011d3306 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4877,22 +4877,15 @@ "xpack.apm.error.prompt.title": "抱歉,发生错误 :(", "xpack.apm.errorCountAlert.name": "错误计数阈值", "xpack.apm.errorCountAlertTrigger.errors": " 错误", - "xpack.apm.errorGroupDetails.avgLabel": "平均", "xpack.apm.errorGroupDetails.culpritLabel": "原因", "xpack.apm.errorGroupDetails.errorGroupTitle": "错误组 {errorGroupId}", "xpack.apm.errorGroupDetails.errorOccurrenceTitle": "错误发生", "xpack.apm.errorGroupDetails.exceptionMessageLabel": "异常消息", "xpack.apm.errorGroupDetails.logMessageLabel": "日志消息", - "xpack.apm.errorGroupDetails.noErrorsLabel": "未找到任何错误", "xpack.apm.errorGroupDetails.occurrencesChartLabel": "发生次数", - "xpack.apm.errorGroupDetails.occurrencesLongLabel": "{occCount} 次{occCount, plural, one {出现} other {出现}}", - "xpack.apm.errorGroupDetails.occurrencesShortLabel": "{occCount} 次发生", "xpack.apm.errorGroupDetails.relatedTransactionSample": "相关的事务样本", "xpack.apm.errorGroupDetails.unhandledLabel": "未处理", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "在 Discover 查看 {occurrencesCount} 个 {occurrencesCount, plural, one {匹配项} other {匹配项}}。", - "xpack.apm.errorRateChart.avgLabel": "平均", - "xpack.apm.errorRateChart.rateLabel": "比率", - "xpack.apm.errorRateChart.title": "事务错误率", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因", "xpack.apm.errorsTable.groupIdColumnDescription": "堆栈跟踪的哈希。将类似错误分组在一起,即使因动态参数造成错误消息不同。", "xpack.apm.errorsTable.groupIdColumnLabel": "组 ID", @@ -4919,7 +4912,6 @@ "xpack.apm.header.badge.readOnly.text": "只读", "xpack.apm.header.badge.readOnly.tooltip": "无法保存", "xpack.apm.helpMenu.upgradeAssistantLink": "升级助手", - "xpack.apm.histogram.plot.noDataLabel": "此时间范围内没有数据。", "xpack.apm.home.alertsMenu.alerts": "告警", "xpack.apm.home.alertsMenu.createAnomalyAlert": "创建异常告警", "xpack.apm.home.alertsMenu.createThresholdAlert": "创建阈值告警", @@ -5260,7 +5252,6 @@ "xpack.apm.transactionDetails.traceNotFound": "找不到所选跟踪", "xpack.apm.transactionDetails.traceSampleTitle": "跟踪样例", "xpack.apm.transactionDetails.transactionLabel": "事务", - "xpack.apm.transactionDetails.transactionsDurationDistributionChart.noSampleTooltip": "此存储桶没有可用样例", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.requestTypeUnitLongLabel": "{transCount, plural, =0 {# 个请求} one {# 个请求} other {# 个请求}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.transactionTypeUnitLongLabel": "{transCount, plural, =0 {# 个事务} one {# 个事务} other {# 个事务}}", "xpack.apm.transactionDetails.transactionsDurationDistributionChart.unitShortLabel": "{transCount} 个{transType, select, request {请求} other {事务}}", From 09aec4defd1d20f6e0fb4089359ffd8de11ff966 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 9 Nov 2020 15:21:15 +0100 Subject: [PATCH 11/87] Indexpattern edit field formatter API fix for scripted field (#82876) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../index_patterns/index_pattern.test.ts | 18 ++++++++++++++++++ .../index_patterns/index_pattern.ts | 18 +++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index 9085ae07bbe3e..145901509d1c5 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -196,6 +196,24 @@ describe('IndexPattern', () => { }); }); + describe('getFormatterForField', () => { + test('should return the default one for empty objects', () => { + indexPattern.setFieldFormat('scriptedFieldWithEmptyFormatter', {}); + expect( + indexPattern.getFormatterForField({ + name: 'scriptedFieldWithEmptyFormatter', + type: 'number', + esTypes: ['long'], + }) + ).toEqual( + expect.objectContaining({ + convert: expect.any(Function), + getConverterFor: expect.any(Function), + }) + ); + }); + }); + describe('toSpec', () => { test('should match snapshot', () => { const formatter = { diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index a0f27078543a9..4508d7b1d9082 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -291,15 +291,15 @@ export class IndexPattern implements IIndexPattern { getFormatterForField( field: IndexPatternField | IndexPatternField['spec'] | IFieldType ): FieldFormat { - const formatSpec = this.fieldFormatMap[field.name]; - if (formatSpec) { - return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); - } else { - return this.fieldFormats.getDefaultInstance( - field.type as KBN_FIELD_TYPES, - field.esTypes as ES_FIELD_TYPES[] - ); + const fieldFormat = this.getFormatterForFieldNoDefault(field.name); + if (fieldFormat) { + return fieldFormat; } + + return this.fieldFormats.getDefaultInstance( + field.type as KBN_FIELD_TYPES, + field.esTypes as ES_FIELD_TYPES[] + ); } /** @@ -308,7 +308,7 @@ export class IndexPattern implements IIndexPattern { */ getFormatterForFieldNoDefault(fieldname: string) { const formatSpec = this.fieldFormatMap[fieldname]; - if (formatSpec) { + if (formatSpec?.id) { return this.fieldFormats.getInstance(formatSpec.id, formatSpec.params); } } From 97e2dc853b5d0d12b87691bb447f6475de0bbfec Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 9 Nov 2020 15:22:21 +0100 Subject: [PATCH 12/87] [Lens] Extend Datasource props validation with VisualizationGroups (#82607) * :sparkles: First pass with visualization validation + error messages * :fire: Remove indexpattern error handling for now * :label: Fix type issues * :white_check_mark: Add getErrorMessage test for data table * :white_check_mark: Add tests for pie and metric error messages * :globe_with_meridians: Fix i18n checks issues * :bug: Fix last issue * :white_check_mark: Add more tests for the XY visualization validation code * :ok_hand: Included all feedback from first review * :pencil2: Off by one message * :globe_with_meridians: Fix i18n duplicate id * :globe_with_meridians: Fix last i18n issue * :bug: Fixed a hook reflow issue * :recycle:+:white_check_mark: Reworked validation flow + tests * :label: Fix type issue * :bug: Improved XY corner cases validation logic * :bug: Fix empty datatable scenario * :sparkles: + :white_check_mark: Improved error messages for invalid datasources + tests * :globe_with_meridians: Add missing i18n translation * :label: Fix type issues * :globe_with_meridians: Fix i18n issues * :sparkles: Filter out suggestions which fail to build * :truck: Migrate datatable validation logic to the building phase, handling it as building state * :label: Fix type issue * :pencil2: Add comment for future enhancements * :pencil2: Updated comment * :world_with_meridians: Refactor axis labels * :globe_with_meridians: Reworked few validation messages * :bug: Fix break down validation + percentage charts * :white_check_mark: Align tests with new validation logic * :recycle: Fix suggestion panel validation to match main panel * :globe_with_meridians: Fix i18n issues * :wrench: Fix some refs for validation checks in suggestions * :bug: Fix missing key prop in multiple errors scenario * :bug: Fix swtich issue from XY to partition * :globe_with_meridians: Fix i18n messages and aligned tests * :bug: Fix suggestions switching bug * :refactor: Add more validation + refactored validation logic in a single place * :pencil2: Add note about lint hooks disable rule * :rotating_light: Fix linting issue * :building_construction: Add infra API for datasource advanced validation * :white_check_mark: Align tests with new API * :white_check_mark: Fix type issues in tests * :ok_hand: Early exists added * :sparkles: Add layers groups to the API * :white_check_mark: Fix some broken test after the validation change * :ok_hand: Move to disctionary shape Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../editor_frame/config_panel/layer_panel.tsx | 1 + .../editor_frame/editor_frame.test.tsx | 9 ++++--- .../editor_frame/state_helpers.ts | 26 +++++++++++++++++-- .../dimension_panel/dimension_panel.test.tsx | 1 + .../dimension_panel/droppable.test.ts | 1 + .../indexpattern_datasource/indexpattern.tsx | 2 +- x-pack/plugins/lens/public/types.ts | 6 ++++- 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index c9d99bcfb6d8d..0332f11aa78b3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -451,6 +451,7 @@ export function LayerPanel( columnId: activeId, filterOperations: activeGroup.filterOperations, suggestedPriority: activeGroup?.suggestedPriority, + dimensionGroups: groups, setState: (newState: unknown) => { props.updateAll( datasourceId, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index f7a6f0597bf9c..b3ea14efbae80 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -601,7 +601,8 @@ describe('editor_frame', () => { setDatasourceState(updatedState); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6); expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith( expect.objectContaining({ state: updatedState, @@ -680,7 +681,8 @@ describe('editor_frame', () => { setDatasourceState({}); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(2); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6); expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith( expect.objectContaining({ frame: expect.objectContaining({ @@ -1193,7 +1195,8 @@ describe('editor_frame', () => { instance.find('[data-test-subj="lnsSuggestion"]').at(2).simulate('click'); }); - expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(1); + // validation requires to calls this getConfiguration API + expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(4); expect(mockVisualization.getConfiguration).toHaveBeenCalledWith( expect.objectContaining({ state: suggestionVisState, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 28ad6c531e255..647c0f3ac9cca 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -6,7 +6,13 @@ import { SavedObjectReference } from 'kibana/public'; import { Ast } from '@kbn/interpreter/common'; -import { Datasource, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../../types'; +import { + Datasource, + DatasourcePublicAPI, + FramePublicAPI, + Visualization, + VisualizationDimensionGroupConfig, +} from '../../types'; import { buildExpression } from './expression_helpers'; import { Document } from '../../persistence/saved_object_store'; import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; @@ -104,8 +110,24 @@ export const validateDatasourceAndVisualization = ( longMessage: string; }> | undefined => { + const layersGroups = + currentVisualizationState && + currentVisualization + ?.getLayerIds(currentVisualizationState) + .reduce>((memo, layerId) => { + const groups = currentVisualization?.getConfiguration({ + frame: frameAPI, + layerId, + state: currentVisualizationState, + }).groups; + if (groups) { + memo[layerId] = groups; + } + return memo; + }, {}); + const datasourceValidationErrors = currentDatasourceState - ? currentDataSource?.getErrorMessages(currentDatasourceState) + ? currentDataSource?.getErrorMessages(currentDatasourceState, layersGroups) : undefined; const visualizationValidationErrors = currentVisualizationState diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 829bd333ce2cc..92a4dad14dd25 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -174,6 +174,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, + dimensionGroups: [], }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index bbd1d4e0ae3ab..dd696f8be357f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -146,6 +146,7 @@ describe('IndexPatternDimensionEditorPanel', () => { } as unknown) as DataPublicPluginStart['fieldFormats'], } as unknown) as DataPublicPluginStart, core: {} as CoreSetup, + dimensionGroups: [], }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index ecca1b878e9a7..fa106e90d518a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -343,7 +343,7 @@ export function getIndexPatternDatasource({ getDatasourceSuggestionsFromCurrentState, getDatasourceSuggestionsForVisualizeField, - getErrorMessages(state) { + getErrorMessages(state, layersGroups) { if (!state) { return; } diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 3c96579fdc943..4ad849c5d441e 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -184,7 +184,10 @@ export interface Datasource { ) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; - getErrorMessages: (state: T) => Array<{ shortMessage: string; longMessage: string }> | undefined; + getErrorMessages: ( + state: T, + layersGroups?: Record + ) => Array<{ shortMessage: string; longMessage: string }> | undefined; /** * uniqueLabels of dimensions exposed for aria-labels of dragged dimensions */ @@ -242,6 +245,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro setState: StateSetter; core: Pick; dateRange: DateRange; + dimensionGroups: VisualizationDimensionGroupConfig[]; }; export type DatasourceDimensionTriggerProps = DatasourceDimensionProps & { From 66f7f9c306b6063622f3825ffef4c563970e6f36 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 9 Nov 2020 15:26:54 +0000 Subject: [PATCH 13/87] fix(NA): missing change from KIBANA_PATH_CONF to KBN_PATH_CONF on bin script (#81500) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/dev/build/tasks/bin/scripts/kibana | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana index c606436c7b83f..a4fc5385500b5 100755 --- a/src/dev/build/tasks/bin/scripts/kibana +++ b/src/dev/build/tasks/bin/scripts/kibana @@ -14,7 +14,7 @@ while [ -h "$SCRIPT" ] ; do done DIR="$(dirname "${SCRIPT}")/.." -CONFIG_DIR=${KIBANA_PATH_CONF:-"$DIR/config"} +CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} NODE="${DIR}/node/bin/node" test -x "$NODE" if [ ! -x "$NODE" ]; then From d5736b10a900f07f154d24357ff3e58e0aca75f5 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 9 Nov 2020 10:46:19 -0500 Subject: [PATCH 14/87] [Enterprise Search] Log retention settings logic (#82364) --- .../log_retention/log_retention_logic.test.ts | 368 ++++++++++++++++++ .../log_retention/log_retention_logic.ts | 117 ++++++ .../settings/log_retention/types.ts | 42 ++ .../utils/convert_log_retention.test.ts | 64 +++ .../utils/convert_log_retention.ts | 45 +++ 5 files changed, 636 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts new file mode 100644 index 0000000000000..367c7b085123f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts @@ -0,0 +1,368 @@ +/* + * 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 { resetContext } from 'kea'; + +import { mockHttpValues } from '../../../../__mocks__'; +jest.mock('../../../../shared/http', () => ({ + HttpLogic: { values: mockHttpValues }, +})); +const { http } = mockHttpValues; + +jest.mock('../../../../shared/flash_messages', () => ({ + flashAPIErrors: jest.fn(), +})); +import { flashAPIErrors } from '../../../../shared/flash_messages'; + +import { ELogRetentionOptions } from './types'; +import { LogRetentionLogic } from './log_retention_logic'; + +describe('LogRetentionLogic', () => { + const TYPICAL_SERVER_LOG_RETENTION = { + analytics: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + }; + + const TYPICAL_CLIENT_LOG_RETENTION = { + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }; + + const DEFAULT_VALUES = { + logRetention: null, + openedModal: null, + isLogRetentionUpdating: false, + }; + + const mount = (defaults?: object) => { + if (!defaults) { + resetContext({}); + } else { + resetContext({ + defaults: { + enterprise_search: { + app_search: { + log_retention_logic: { + ...defaults, + }, + }, + }, + }, + }); + } + LogRetentionLogic.mount(); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(LogRetentionLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('setOpenedModal', () => { + describe('openedModal', () => { + it('should be set to the provided value', () => { + mount(); + + LogRetentionLogic.actions.setOpenedModal(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: ELogRetentionOptions.Analytics, + }); + }); + }); + }); + + describe('closeModals', () => { + describe('openedModal', () => { + it('resets openedModal to null', () => { + mount({ + openedModal: 'analytics', + }); + + LogRetentionLogic.actions.closeModals(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: null, + }); + }); + }); + + describe('isLogRetentionUpdating', () => { + it('resets isLogRetentionUpdating to false', () => { + mount({ + isLogRetentionUpdating: true, + }); + + LogRetentionLogic.actions.closeModals(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: false, + }); + }); + }); + }); + + describe('clearLogRetentionUpdating', () => { + describe('isLogRetentionUpdating', () => { + it('resets isLogRetentionUpdating to false', () => { + mount({ + isLogRetentionUpdating: true, + }); + + LogRetentionLogic.actions.clearLogRetentionUpdating(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: false, + }); + }); + }); + }); + + describe('updateLogRetention', () => { + describe('logRetention', () => { + it('updates the logRetention values that are passed', () => { + mount({ + logRetention: {}, + }); + + LogRetentionLogic.actions.updateLogRetention({ + api: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + }); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + logRetention: { + api: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + }, + }); + }); + }); + }); + + describe('saveLogRetention', () => { + beforeEach(() => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + }); + + describe('openedModal', () => { + it('should be reset to null', () => { + mount({ + openedModal: ELogRetentionOptions.Analytics, + }); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + openedModal: null, + }); + }); + }); + + it('will call an API endpoint and update log retention', async () => { + jest.spyOn(LogRetentionLogic.actions, 'updateLogRetention'); + const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); + http.put.mockReturnValue(promise); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + expect(http.put).toHaveBeenCalledWith('/api/app_search/log_settings', { + body: JSON.stringify({ + analytics: { + enabled: true, + }, + }), + }); + + await promise; + expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( + TYPICAL_CLIENT_LOG_RETENTION + ); + + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + + it('handles errors', async () => { + const promise = Promise.reject('An error occured'); + http.put.mockReturnValue(promise); + + LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + }); + + describe('toggleLogRetention', () => { + describe('isLogRetentionUpdating', () => { + it('sets isLogRetentionUpdating to true', () => { + mount({ + isLogRetentionUpdating: false, + }); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: true, + }); + }); + }); + + it('will call setOpenedModal if already enabled', () => { + mount({ + logRetention: { + [ELogRetentionOptions.Analytics]: { + enabled: true, + }, + }, + }); + jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.actions.setOpenedModal).toHaveBeenCalledWith( + ELogRetentionOptions.Analytics + ); + }); + }); + + describe('fetchLogRetention', () => { + describe('isLogRetentionUpdating', () => { + it('sets isLogRetentionUpdating to true', () => { + mount({ + isLogRetentionUpdating: false, + }); + + LogRetentionLogic.actions.fetchLogRetention(); + + expect(LogRetentionLogic.values).toEqual({ + ...DEFAULT_VALUES, + isLogRetentionUpdating: true, + }); + }); + }); + + it('will call an API endpoint and update log retention', async () => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + jest + .spyOn(LogRetentionLogic.actions, 'updateLogRetention') + .mockImplementationOnce(() => {}); + + const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); + http.get.mockReturnValue(promise); + + LogRetentionLogic.actions.fetchLogRetention(); + + expect(http.get).toHaveBeenCalledWith('/api/app_search/log_settings'); + await promise; + expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( + TYPICAL_CLIENT_LOG_RETENTION + ); + + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + + it('handles errors', async () => { + mount(); + jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); + const promise = Promise.reject('An error occured'); + http.get.mockReturnValue(promise); + + LogRetentionLogic.actions.fetchLogRetention(); + + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); + }); + }); + + it('will call saveLogRetention if NOT already enabled', () => { + mount({ + logRetention: { + [ELogRetentionOptions.Analytics]: { + enabled: false, + }, + }, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + + expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( + ELogRetentionOptions.Analytics, + true + ); + }); + + it('will do nothing if logRetention option is not yet set', () => { + mount({ + logRetention: {}, + }); + jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); + jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); + + LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.API); + + expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); + expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts new file mode 100644 index 0000000000000..28830f2edb1d9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts @@ -0,0 +1,117 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { ELogRetentionOptions, ILogRetention, ILogRetentionServer } from './types'; +import { HttpLogic } from '../../../../shared/http'; +import { flashAPIErrors } from '../../../../shared/flash_messages'; +import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; + +interface ILogRetentionActions { + clearLogRetentionUpdating(): { value: boolean }; + closeModals(): { value: boolean }; + fetchLogRetention(): { value: boolean }; + saveLogRetention( + option: ELogRetentionOptions, + enabled: boolean + ): { option: ELogRetentionOptions; enabled: boolean }; + setOpenedModal(option: ELogRetentionOptions): { option: ELogRetentionOptions }; + toggleLogRetention(option: ELogRetentionOptions): { option: ELogRetentionOptions }; + updateLogRetention(logRetention: ILogRetention): { logRetention: ILogRetention }; +} + +interface ILogRetentionValues { + logRetention: ILogRetention | null; + isLogRetentionUpdating: boolean; + openedModal: ELogRetentionOptions | null; +} + +export const LogRetentionLogic = kea>({ + path: ['enterprise_search', 'app_search', 'log_retention_logic'], + actions: () => ({ + clearLogRetentionUpdating: true, + closeModals: true, + fetchLogRetention: true, + saveLogRetention: (option, enabled) => ({ enabled, option }), + setOpenedModal: (option) => ({ option }), + toggleLogRetention: (option) => ({ option }), + updateLogRetention: (logRetention) => ({ logRetention }), + }), + reducers: () => ({ + logRetention: [ + null, + { + updateLogRetention: (_, { logRetention }) => logRetention, + }, + ], + isLogRetentionUpdating: [ + false, + { + clearLogRetentionUpdating: () => false, + closeModals: () => false, + fetchLogRetention: () => true, + toggleLogRetention: () => true, + }, + ], + openedModal: [ + null, + { + closeModals: () => null, + saveLogRetention: () => null, + setOpenedModal: (_, { option }) => option, + }, + ], + }), + listeners: ({ actions, values }) => ({ + fetchLogRetention: async () => { + try { + const { http } = HttpLogic.values; + const response = await http.get('/api/app_search/log_settings'); + actions.updateLogRetention( + convertLogRetentionFromServerToClient(response as ILogRetentionServer) + ); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.clearLogRetentionUpdating(); + } + }, + saveLogRetention: async ({ enabled, option }) => { + const updateData = { [option]: { enabled } }; + + try { + const { http } = HttpLogic.values; + const response = await http.put('/api/app_search/log_settings', { + body: JSON.stringify(updateData), + }); + actions.updateLogRetention( + convertLogRetentionFromServerToClient(response as ILogRetentionServer) + ); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.clearLogRetentionUpdating(); + } + }, + toggleLogRetention: ({ option }) => { + const logRetention = values.logRetention?.[option]; + + // If the user has found a way to call this before we've retrieved + // log retention settings from the server, short circuit this and return early + if (!logRetention) { + return; + } + + const optionIsAlreadyEnabled = logRetention.enabled; + if (optionIsAlreadyEnabled) { + actions.setOpenedModal(option); + } else { + actions.saveLogRetention(option, true); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.ts new file mode 100644 index 0000000000000..7d4f30f88f38f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/types.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. + */ + +export enum ELogRetentionOptions { + Analytics = 'analytics', + API = 'api', +} + +export interface ILogRetention { + [ELogRetentionOptions.Analytics]: ILogRetentionSettings; + [ELogRetentionOptions.API]: ILogRetentionSettings; +} + +export interface ILogRetentionPolicy { + isDefault: boolean; + minAgeDays: number | null; +} + +export interface ILogRetentionSettings { + disabledAt?: string | null; + enabled?: boolean; + retentionPolicy?: ILogRetentionPolicy | null; +} + +export interface ILogRetentionServer { + [ELogRetentionOptions.Analytics]: ILogRetentionServerSettings; + [ELogRetentionOptions.API]: ILogRetentionServerSettings; +} + +export interface ILogRetentionServerPolicy { + is_default: boolean; + min_age_days: number | null; +} + +export interface ILogRetentionServerSettings { + disabled_at: string | null; + enabled: boolean; + retention_policy: ILogRetentionServerPolicy | null; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts new file mode 100644 index 0000000000000..b49b2afe50831 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.test.ts @@ -0,0 +1,64 @@ +/* + * 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 { convertLogRetentionFromServerToClient } from './convert_log_retention'; + +describe('convertLogRetentionFromServerToClient', () => { + it('converts log retention from server to client', () => { + expect( + convertLogRetentionFromServerToClient({ + analytics: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: 180 }, + }, + }) + ).toEqual({ + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }); + }); + + it('handles null retention policies and null min_age_days', () => { + expect( + convertLogRetentionFromServerToClient({ + analytics: { + disabled_at: null, + enabled: true, + retention_policy: null, + }, + api: { + disabled_at: null, + enabled: true, + retention_policy: { is_default: true, min_age_days: null }, + }, + }) + ).toEqual({ + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: null }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts new file mode 100644 index 0000000000000..1c0818fc286f2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/utils/convert_log_retention.ts @@ -0,0 +1,45 @@ +/* + * 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 { + ELogRetentionOptions, + ILogRetention, + ILogRetentionPolicy, + ILogRetentionServer, + ILogRetentionServerPolicy, + ILogRetentionServerSettings, + ILogRetentionSettings, +} from '../types'; + +export const convertLogRetentionFromServerToClient = ( + logRetention: ILogRetentionServer +): ILogRetention => ({ + [ELogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( + logRetention[ELogRetentionOptions.Analytics] + ), + [ELogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( + logRetention[ELogRetentionOptions.API] + ), +}); + +const convertLogRetentionSettingsFromServerToClient = ({ + disabled_at: disabledAt, + enabled, + retention_policy: retentionPolicy, +}: ILogRetentionServerSettings): ILogRetentionSettings => ({ + disabledAt, + enabled, + retentionPolicy: + retentionPolicy === null ? null : convertLogRetentionPolicyFromServerToClient(retentionPolicy), +}); + +const convertLogRetentionPolicyFromServerToClient = ({ + min_age_days: minAgeDays, + is_default: isDefault, +}: ILogRetentionServerPolicy): ILogRetentionPolicy => ({ + isDefault, + minAgeDays, +}); From 0a71f2c45e340429941d021ab186a67851710a02 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 09:50:57 -0600 Subject: [PATCH 15/87] [deb/rpm] On upgrade, restart kibana service (#82049) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../os_packages/package_scripts/post_install.sh | 13 +++++++++++++ src/dev/build/tasks/os_packages/run_fpm.ts | 3 ++- .../service_templates/systemd/etc/default/kibana | 10 +--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 939226b565f79..728278dae746b 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -1,6 +1,11 @@ #!/bin/sh set -e +# source the default env file +if [ -f "<%= envFile %>" ]; then + . "<%= envFile %>" +fi + export KBN_PATH_CONF=${KBN_PATH_CONF:-<%= configDir %>} set_chmod() { @@ -71,4 +76,12 @@ if [ "$IS_UPGRADE" = "true" ]; then if command -v systemctl >/dev/null; then systemctl daemon-reload fi + + if [ "$RESTART_ON_UPGRADE" = "true" ]; then + echo -n "Restarting kibana service..." + if command -v systemctl >/dev/null; then + systemctl restart kibana.service || true + fi + echo " OK" + fi fi diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index e5de760ea11d0..f16eaea1daa2f 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -111,7 +111,8 @@ export async function runFpm( `dataDir=/var/lib/kibana`, '--template-value', `logDir=/var/log/kibana`, - + '--template-value', + `envFile=/etc/default/kibana`, // config and data directories are copied to /usr/share and /var/lib // below, so exclude them from the main package source located in // /usr/share/kibana/config. PATHS MUST BE RELATIVE, so drop the leading slash diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana index 9c9f58ded350b..7d0c955964ae6 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana @@ -1,13 +1,5 @@ user="kibana" group="kibana" -chroot="/" -chdir="/" -nice="" - - -# If this is set to 1, then when `stop` is called, if the process has -# not exited within a reasonable time, SIGKILL will be sent next. -# The default behavior is to simply log a message "program stop failed; still running" -KILL_ON_STOP_TIMEOUT=0 KBN_PATH_CONF="/etc/kibana" +RESTART_ON_UPGRADE="true" From d2d30e7f8056245f2e1974189c31202cba245fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 9 Nov 2020 16:54:48 +0100 Subject: [PATCH 16/87] Bump cypress dependencies (#82815) --- package.json | 10 +++++----- yarn.lock | 55 +++++++++++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index ade567c840da7..3500ed78ad7d1 100644 --- a/package.json +++ b/package.json @@ -345,8 +345,8 @@ "@babel/register": "^7.10.5", "@babel/traverse": "^7.11.5", "@babel/types": "^7.11.0", - "@cypress/snapshot": "^2.1.3", - "@cypress/webpack-preprocessor": "^5.4.1", + "@cypress/snapshot": "^2.1.7", + "@cypress/webpack-preprocessor": "^5.4.10", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/charts": "24.0.0", @@ -609,9 +609,9 @@ "cpy": "^8.1.1", "cronstrue": "^1.51.0", "css-loader": "^3.4.2", - "cypress": "^5.4.0", + "cypress": "^5.5.0", "cypress-cucumber-preprocessor": "^2.5.2", - "cypress-multi-reporters": "^1.2.3", + "cypress-multi-reporters": "^1.4.0", "d3": "3.5.17", "d3-cloud": "1.2.5", "d3-scale": "1.0.7", @@ -633,7 +633,7 @@ "eslint-module-utils": "2.5.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.4.0", - "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.19.1", "eslint-plugin-jest": "^24.0.2", diff --git a/yarn.lock b/yarn.lock index 0b429c96c1847..edbc186c22243 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,7 +1310,7 @@ tunnel-agent "^0.6.0" uuid "^3.3.2" -"@cypress/snapshot@^2.1.3": +"@cypress/snapshot@^2.1.7": version "2.1.7" resolved "https://registry.yarnpkg.com/@cypress/snapshot/-/snapshot-2.1.7.tgz#e7360eb628b062f28f03036382619ec72cfb1831" integrity sha512-f8AcfIg7wOOHSdBODlIwCJE/rG5Yb+kUY+WVTKynB2pLLoDy9nc8CtcazqX19q2Lh++nTJLNRihpbbWvk33mbg== @@ -1324,10 +1324,10 @@ snap-shot-compare "2.8.3" snap-shot-store "1.2.3" -"@cypress/webpack-preprocessor@^5.4.1": - version "5.4.6" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.6.tgz#667f8007cbe6ee219ce7e45a7f1400d3e2401032" - integrity sha512-78hWoTUUEncv647badwVbyszvmwI1r9GaY/xy7V0sz0VVC90ByuDkLpvN+J0VP6enthob4dIPXcm0f9Tb1UKQQ== +"@cypress/webpack-preprocessor@^5.4.10": + version "5.4.10" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.10.tgz#ed0a3ed495f5455899f7c567830b37477c3f26f5" + integrity sha512-6j809mAQcZsAbpIaQFlKwFQPKv1Y+ZyLr9bpW7d2utMGVll0W8Up4RPhIuAf4q9Tx+DOBQoiCpy/n0cRPxporw== dependencies: bluebird "^3.7.1" debug "^4.1.1" @@ -10711,23 +10711,23 @@ cypress-cucumber-preprocessor@^2.5.2: minimist "^1.2.0" through "^2.3.8" -cypress-multi-reporters@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.2.3.tgz#4ba39373631c6521d21931d73f6b0bafa1ccbf83" - integrity sha512-W3ItWsbSgMfsQFTuB89OXY5gyqLuM0O2lNEn+mcQAYeMs36TxVLAg3q+Hk0Om+NcWj8OLhM06lBQpnu9+i4gug== +cypress-multi-reporters@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cypress-multi-reporters/-/cypress-multi-reporters-1.4.0.tgz#5f1d0484a20959cfe782f1bf65ad16c6ad804da7" + integrity sha512-CjpQduW43KVzY45hhKC/qf8MSebRpx6JyEz6py8F+0GrYS8rE5TZ8wXv9dPUs/PaT6w+dR8KIgLSMr967Om7iA== dependencies: debug "^4.1.1" - lodash "^4.17.11" + lodash "^4.17.15" cypress-promise@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== -cypress@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.4.0.tgz#8833a76e91129add601f823d43c53eb512d162c5" - integrity sha512-BJR+u3DRSYMqaBS1a3l1rbh5AkMRHugbxcYYzkl+xYlO6dzcJVE8uAhghzVI/hxijCyBg1iuSe4TRp/g1PUg8Q== +cypress@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.5.0.tgz#1da0355794a43247f8a80cb7f505e83e1cf847cb" + integrity sha512-UHEiTca8AUTevbT2pWkHQlxoHtXmbq+h6Eiu/Mz8DqpNkF98zjTBLv/HFiKJUU5rQzp9EwSWtms33p5TWCJ8tQ== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5" @@ -10759,10 +10759,10 @@ cypress@^5.4.0: minimist "^1.2.5" moment "^2.27.0" ospath "^1.2.2" - pretty-bytes "^5.3.0" + pretty-bytes "^5.4.1" ramda "~0.26.1" request-progress "^3.0.0" - supports-color "^7.1.0" + supports-color "^7.2.0" tmp "~0.2.1" untildify "^4.0.0" url "^0.11.0" @@ -12704,10 +12704,10 @@ eslint-plugin-ban@^1.4.0: dependencies: requireindex "~1.2.0" -eslint-plugin-cypress@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.8.1.tgz#981a0f3658b40de430bcf05cabc96b396487c91f" - integrity sha512-jDpcP+MmjmqQO/x3bwIXgp4cl7Q66RYS5/IsuOQP4Qo2sEqE3DI8tTxBQ1EhnV5qEDd2Z2TYHR+5vYI6oCN4uw== +eslint-plugin-cypress@^2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" + integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA== dependencies: globals "^11.12.0" @@ -22404,10 +22404,10 @@ prettier@~2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -pretty-bytes@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== +pretty-bytes@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== pretty-error@^2.1.1: version "2.1.1" @@ -26551,6 +26551,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" From b5e6d7c29df903c0809fa9ca2d37f8a5c117e852 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 10:05:26 -0600 Subject: [PATCH 17/87] [deb/rpm] Remove /var prefix from tmpfiles.d (#82196) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- config/kibana.yml | 2 +- .../service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/kibana.yml b/config/kibana.yml index 58ae8b9346f51..ce9fe28dae916 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -80,7 +80,7 @@ #elasticsearch.logQueries: false # Specifies the path where Kibana creates the process ID file. -#pid.file: /var/run/kibana.pid +#pid.file: /run/kibana/kibana.pid # Enables you to specify a file where Kibana stores log output. #logging.dest: stdout diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf index b5422df52fe11..fe033e30fbf58 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/usr/lib/tmpfiles.d/kibana.conf @@ -1 +1 @@ -d /var/run/kibana 0755 kibana kibana - - \ No newline at end of file +d /run/kibana 0755 kibana kibana - - \ No newline at end of file From 441a0d4ec9f713c864f742846084ce598222afa4 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 9 Nov 2020 11:07:04 -0500 Subject: [PATCH 18/87] [Fleet] Move ingestManager plugin to fleet (#82886) --- .github/CODEOWNERS | 4 ++-- .github/paths-labeller.yml | 2 +- docs/developer/plugin-list.asciidoc | 2 +- x-pack/.i18nrc.json | 2 +- .../plugins/{ingest_manager => fleet}/CHANGELOG.md | 0 x-pack/plugins/{ingest_manager => fleet}/README.md | 2 +- .../common/constants/agent.ts | 0 .../common/constants/agent_policy.ts | 0 .../common/constants/enrollment_api_key.ts | 0 .../common/constants/epm.ts | 0 .../common/constants/index.ts | 0 .../common/constants/output.ts | 0 .../common/constants/package_policy.ts | 0 .../common/constants/plugin.ts | 0 .../common/constants/routes.ts | 0 .../common/constants/settings.ts | 0 .../{ingest_manager => fleet}/common/index.ts | 0 .../{ingest_manager => fleet}/common/mocks.ts | 0 .../common/openapi/README.md | 0 .../common/openapi/bundled.json | 0 .../common/openapi/bundled.yaml | 0 .../common/openapi/components/README.md | 0 .../common/openapi/components/headers/kbn_xsrf.yaml | 0 .../common/openapi/components/parameters/kuery.yaml | 0 .../openapi/components/parameters/page_index.yaml | 0 .../openapi/components/parameters/page_size.yaml | 0 .../openapi/components/schemas/access_api_key.yaml | 0 .../common/openapi/components/schemas/agent.yaml | 0 .../openapi/components/schemas/agent_event.yaml | 0 .../openapi/components/schemas/agent_metadata.yaml | 0 .../openapi/components/schemas/agent_policy.yaml | 0 .../openapi/components/schemas/agent_status.yaml | 0 .../openapi/components/schemas/agent_type.yaml | 0 .../components/schemas/bulk_upgrade_agents.yaml | 0 .../components/schemas/enrollment_api_key.yaml | 0 .../openapi/components/schemas/new_agent_event.yaml | 0 .../components/schemas/new_agent_policy.yaml | 0 .../components/schemas/new_package_policy.yaml | 0 .../openapi/components/schemas/package_info.yaml | 0 .../openapi/components/schemas/package_policy.yaml | 0 .../openapi/components/schemas/search_result.yaml | 0 .../openapi/components/schemas/upgrade_agent.yaml | 0 .../common/openapi/entrypoint.yaml | 0 .../common/openapi/paths/README.md | 0 .../common/openapi/paths/agent_policies.yaml | 0 .../common/openapi/paths/agent_policies@delete.yaml | 0 .../paths/agent_policies@{agent_policy_id}.yaml | 0 .../agent_policies@{agent_policy_id}@copy.yaml | 0 .../common/openapi/paths/agent_status.yaml | 0 .../common/openapi/paths/agents.yaml | 0 .../common/openapi/paths/agents@bulk_upgrade.yaml | 0 .../common/openapi/paths/agents@enroll.yaml | 0 .../common/openapi/paths/agents@setup.yaml | 0 .../common/openapi/paths/agents@{agent_id}.yaml | 0 .../openapi/paths/agents@{agent_id}@acks.yaml | 0 .../openapi/paths/agents@{agent_id}@checkin.yaml | 0 .../openapi/paths/agents@{agent_id}@events.yaml | 0 .../openapi/paths/agents@{agent_id}@unenroll.yaml | 0 .../openapi/paths/agents@{agent_id}@upgrade.yaml | 0 .../common/openapi/paths/enrollment_api_keys.yaml | 0 .../openapi/paths/enrollment_api_keys@{key_id}.yaml | 0 .../common/openapi/paths/epm@categories.yaml | 0 .../common/openapi/paths/epm@packages.yaml | 0 .../common/openapi/paths/epm@packages@{pkgkey}.yaml | 0 .../common/openapi/paths/install@{os_type}.yaml | 0 .../common/openapi/paths/package_policies.yaml | 0 .../paths/package_policies@{package_policy_id}.yaml | 0 .../common/openapi/paths/setup.yaml | 0 .../common/services/agent_status.ts | 0 .../common/services/decode_cloud_id.test.ts | 0 .../common/services/decode_cloud_id.ts | 0 .../full_agent_policy_kibana_config.test.ts | 0 .../services/full_agent_policy_kibana_config.ts | 0 .../common/services/full_agent_policy_to_yaml.ts | 0 .../common/services/index.ts | 0 .../common/services/is_agent_upgradeable.test.ts | 0 .../common/services/is_agent_upgradeable.ts | 0 .../common/services/is_diff_path_protocol.test.ts | 0 .../common/services/is_diff_path_protocol.ts | 0 .../common/services/is_valid_namespace.test.ts | 0 .../common/services/is_valid_namespace.ts | 0 .../common/services/license.ts | 0 .../common/services/limited_package.ts | 0 .../package_policies_to_agent_inputs.test.ts | 0 .../services/package_policies_to_agent_inputs.ts | 0 .../services/package_to_package_policy.test.ts | 0 .../common/services/package_to_package_policy.ts | 0 .../common/services/routes.ts | 0 .../{ingest_manager => fleet}/common/types/index.ts | 0 .../common/types/models/agent.ts | 0 .../common/types/models/agent_policy.ts | 0 .../common/types/models/data_stream.ts | 0 .../common/types/models/enrollment_api_key.ts | 0 .../common/types/models/epm.ts | 0 .../common/types/models/index.ts | 0 .../common/types/models/output.ts | 0 .../common/types/models/package_policy.ts | 0 .../common/types/models/settings.ts | 0 .../common/types/rest_spec/agent.ts | 0 .../common/types/rest_spec/agent_policy.ts | 0 .../common/types/rest_spec/app.ts | 0 .../common/types/rest_spec/common.ts | 0 .../common/types/rest_spec/data_stream.ts | 0 .../common/types/rest_spec/enrollment_api_key.ts | 0 .../common/types/rest_spec/epm.ts | 0 .../common/types/rest_spec/fleet_setup.ts | 0 .../common/types/rest_spec/index.ts | 0 .../common/types/rest_spec/ingest_setup.ts | 0 .../common/types/rest_spec/install_script.ts | 0 .../common/types/rest_spec/output.ts | 0 .../common/types/rest_spec/package_policy.ts | 0 .../common/types/rest_spec/settings.ts | 0 .../dev_docs/actions_and_events.md | 0 .../dev_docs/api/agents_acks.md | 0 .../dev_docs/api/agents_checkin.md | 0 .../dev_docs/api/agents_enroll.md | 0 .../dev_docs/api/agents_list.md | 0 .../dev_docs/api/agents_unenroll.md | 0 .../{ingest_manager => fleet}/dev_docs/api/epm.md | 0 .../dev_docs/api_integration_tests.md | 0 .../{ingest_manager => fleet}/dev_docs/api_keys.md | 0 .../dev_docs/definitions.md | 0 .../{ingest_manager => fleet}/dev_docs/epm.md | 0 .../dev_docs/fleet_agent_communication.md | 0 .../dev_docs/fleet_agents_interactions_detailed.md | 0 .../dev_docs/indexing_strategy.md | 0 .../dev_docs/schema/agent_checkin.mml | 0 .../dev_docs/schema/agent_checkin.png | Bin .../dev_docs/schema/agent_enroll.mml | 0 .../dev_docs/schema/agent_enroll.png | Bin .../dev_docs/schema/saved_objects.mml | 0 .../dev_docs/schema/saved_objects.png | Bin .../{ingest_manager => fleet}/dev_docs/tracing.md | 0 .../plugins/{ingest_manager => fleet}/kibana.json | 0 .../plugins/{ingest_manager => fleet}/package.json | 0 .../applications/fleet}/components/alpha_flyout.tsx | 0 .../fleet}/components/alpha_messaging.tsx | 0 .../fleet}/components/context_menu_actions.tsx | 0 .../components/enrollment_instructions/index.tsx | 0 .../enrollment_instructions/manual/index.tsx | 0 .../public/applications/fleet}/components/error.tsx | 0 .../applications/fleet}/components/header.tsx | 0 .../fleet}/components/home_integration/index.tsx | 0 .../tutorial_directory_header_link.tsx | 0 .../home_integration/tutorial_directory_notice.tsx | 0 .../home_integration/tutorial_module_notice.tsx | 0 .../public/applications/fleet}/components/index.ts | 0 .../applications/fleet}/components/loading.tsx | 0 .../applications/fleet}/components/package_icon.tsx | 0 .../applications/fleet}/components/search_bar.tsx | 0 .../fleet}/components/settings_flyout.tsx | 0 .../public/applications/fleet}/constants/index.ts | 0 .../applications/fleet}/constants/page_paths.ts | 0 .../public/applications/fleet}/hooks/index.ts | 0 .../applications/fleet}/hooks/use_breadcrumbs.tsx | 0 .../applications/fleet}/hooks/use_capabilities.ts | 0 .../public/applications/fleet}/hooks/use_config.ts | 0 .../public/applications/fleet}/hooks/use_core.ts | 0 .../applications/fleet}/hooks/use_debounce.tsx | 0 .../public/applications/fleet}/hooks/use_deps.ts | 0 .../applications/fleet}/hooks/use_fleet_status.tsx | 0 .../public/applications/fleet}/hooks/use_input.ts | 0 .../fleet}/hooks/use_intra_app_state.tsx | 0 .../applications/fleet}/hooks/use_kibana_link.ts | 0 .../applications/fleet}/hooks/use_kibana_version.ts | 0 .../public/applications/fleet}/hooks/use_license.ts | 0 .../public/applications/fleet}/hooks/use_link.ts | 0 .../fleet}/hooks/use_package_icon_type.ts | 0 .../applications/fleet}/hooks/use_pagination.tsx | 0 .../fleet}/hooks/use_request/agent_policy.ts | 0 .../applications/fleet}/hooks/use_request/agents.ts | 0 .../applications/fleet}/hooks/use_request/app.ts | 0 .../fleet}/hooks/use_request/data_stream.ts | 0 .../fleet}/hooks/use_request/enrollment_api_keys.ts | 0 .../applications/fleet}/hooks/use_request/epm.ts | 0 .../applications/fleet}/hooks/use_request/index.ts | 0 .../fleet}/hooks/use_request/outputs.ts | 0 .../fleet}/hooks/use_request/package_policy.ts | 0 .../fleet}/hooks/use_request/settings.ts | 0 .../applications/fleet}/hooks/use_request/setup.ts | 0 .../fleet}/hooks/use_request/use_request.ts | 0 .../applications/fleet}/hooks/use_sorting.tsx | 0 .../applications/fleet}/hooks/use_url_params.ts | 0 .../public/applications/fleet}/index.tsx | 0 .../public/applications/fleet}/layouts/default.tsx | 0 .../public/applications/fleet}/layouts/index.tsx | 0 .../applications/fleet}/layouts/with_header.tsx | 0 .../applications/fleet}/layouts/without_header.tsx | 0 .../agent_policy/components/actions_menu.tsx | 2 +- .../components/agent_policy_copy_provider.tsx | 0 .../components/agent_policy_delete_provider.tsx | 0 .../agent_policy/components/agent_policy_form.tsx | 0 .../components/agent_policy_yaml_flyout.tsx | 0 .../components/confirm_deploy_modal.tsx | 0 .../components/danger_eui_context_menu_item.tsx | 0 .../sections/agent_policy/components/index.ts | 0 .../agent_policy/components/linked_agent_count.tsx | 0 .../components/package_policy_delete_provider.tsx | 0 .../components/custom_package_policy.tsx | 0 .../create_package_policy_page/components/index.ts | 0 .../components/layout.tsx | 0 .../components/package_policy_input_config.tsx | 0 .../components/package_policy_input_panel.tsx | 0 .../components/package_policy_input_stream.tsx | 0 .../components/package_policy_input_var_field.tsx | 0 .../create_package_policy_page/index.tsx | 0 .../services/has_invalid_but_required_var.test.ts | 0 .../services/has_invalid_but_required_var.ts | 0 .../create_package_policy_page/services/index.ts | 0 .../services/is_advanced_var.test.ts | 0 .../services/is_advanced_var.ts | 0 .../services/validate_package_policy.test.ts | 0 .../services/validate_package_policy.ts | 0 .../step_configure_package.tsx | 0 .../step_define_package_policy.tsx | 0 .../step_select_agent_policy.tsx | 0 .../step_select_package.tsx | 0 .../create_package_policy_page/types.ts | 0 .../agent_policy/details_page/components/index.ts | 0 .../components/package_policies/index.tsx | 0 .../package_policies/no_package_policies.tsx | 0 .../package_policies/package_policies_table.tsx | 0 .../details_page/components/settings/index.tsx | 0 .../agent_policy/details_page/hooks/index.ts | 0 .../details_page/hooks/use_agent_status.tsx | 0 .../agent_policy/details_page/hooks/use_config.tsx | 0 .../sections/agent_policy/details_page/index.tsx | 0 .../agent_policy/edit_package_policy_page/index.tsx | 0 .../fleet}/sections/agent_policy/index.tsx | 0 .../list_page/components/create_agent_policy.tsx | 0 .../agent_policy/list_page/components/index.ts | 0 .../sections/agent_policy/list_page/index.tsx | 0 .../agent_details_page/components/actions_menu.tsx | 0 .../agent_details_page/components/agent_details.tsx | 0 .../components/agent_events_table.tsx | 0 .../agents}/agent_details_page/components/helper.ts | 0 .../agents}/agent_details_page/components/index.ts | 0 .../components/metadata_flyout.tsx | 0 .../agent_details_page/components/metadata_form.tsx | 0 .../agent_details_page/components/type_labels.tsx | 0 .../agents}/agent_details_page/hooks/index.ts | 0 .../agents}/agent_details_page/hooks/use_agent.tsx | 0 .../sections/agents}/agent_details_page/index.tsx | 0 .../agent_list_page/components/bulk_actions.tsx | 0 .../sections/agents}/agent_list_page/index.tsx | 0 .../agent_policy_selection.tsx | 0 .../components/agent_enrollment_flyout/index.tsx | 0 .../managed_instructions.tsx | 0 .../standalone_instructions.tsx | 0 .../components/agent_enrollment_flyout/steps.tsx | 0 .../sections/agents}/components/agent_health.tsx | 0 .../components/agent_policy_package_badges.tsx | 0 .../agent_reassign_policy_flyout/index.tsx | 0 .../components/agent_unenroll_modal/index.tsx | 0 .../components/agent_upgrade_modal/index.tsx | 0 .../sections/agents}/components/donut_chart.tsx | 0 .../fleet/sections/agents}/components/index.tsx | 0 .../sections/agents}/components/list_layout.tsx | 0 .../fleet/sections/agents}/components/loading.tsx | 0 .../components/confirm_delete_modal.tsx | 0 .../components/new_enrollment_key_flyout.tsx | 0 .../agents}/enrollment_token_list_page/index.tsx | 0 .../error_pages/components/no_data_layout.tsx | 0 .../agents}/error_pages/enforce_security.tsx | 0 .../agents}/error_pages/invalid_license.tsx | 0 .../sections/agents}/error_pages/no_access.tsx | 0 .../applications/fleet/sections/agents}/index.tsx | 0 .../fleet/sections/agents}/setup_page/index.tsx | 0 .../fleet}/sections/data_stream/index.tsx | 0 .../components/data_stream_row_actions.tsx | 0 .../fleet}/sections/data_stream/list_page/index.tsx | 0 .../sections/epm/components/assets_facet_group.tsx | 0 .../fleet}/sections/epm/components/icon_panel.tsx | 0 .../fleet}/sections/epm/components/icons.tsx | 0 .../fleet}/sections/epm/components/package_card.tsx | 0 .../sections/epm/components/package_list_grid.tsx | 0 .../fleet}/sections/epm/components/release_badge.ts | 0 .../fleet}/sections/epm/components/requirements.tsx | 0 .../fleet}/sections/epm/components/version.tsx | 0 .../applications/fleet}/sections/epm/constants.tsx | 0 .../fleet}/sections/epm/hooks/index.tsx | 0 .../fleet}/sections/epm/hooks/use_links.tsx | 0 .../fleet}/sections/epm/hooks/use_local_search.tsx | 0 .../sections/epm/hooks/use_package_install.tsx | 0 .../applications/fleet}/sections/epm/index.tsx | 0 .../epm/screens/detail/confirm_package_install.tsx | 0 .../screens/detail/confirm_package_uninstall.tsx | 0 .../fleet}/sections/epm/screens/detail/content.tsx | 0 .../epm/screens/detail/content_collapse.tsx | 0 .../fleet}/sections/epm/screens/detail/index.tsx | 0 .../epm/screens/detail/installation_button.tsx | 0 .../fleet}/sections/epm/screens/detail/layout.tsx | 0 .../epm/screens/detail/markdown_renderers.tsx | 0 .../sections/epm/screens/detail/overview_panel.tsx | 0 .../epm/screens/detail/package_policies_panel.tsx | 0 .../fleet}/sections/epm/screens/detail/readme.tsx | 0 .../sections/epm/screens/detail/screenshots.tsx | 0 .../sections/epm/screens/detail/settings_panel.tsx | 0 .../sections/epm/screens/detail/side_nav_links.tsx | 0 .../sections/epm/screens/home/category_facets.tsx | 0 .../fleet}/sections/epm/screens/home/header.tsx | 0 .../fleet}/sections/epm/screens/home/index.tsx | 0 .../public/applications/fleet}/sections/index.tsx | 2 +- .../overview/components/agent_policy_section.tsx | 2 +- .../sections/overview/components/agent_section.tsx | 2 +- .../overview/components/datastream_section.tsx | 2 +- .../overview/components/integration_section.tsx | 2 +- .../sections/overview/components/overview_panel.tsx | 0 .../sections/overview/components/overview_stats.tsx | 0 .../applications/fleet}/sections/overview/index.tsx | 2 +- .../public/applications/fleet}/services/index.ts | 0 .../public/applications/fleet}/types/index.ts | 0 .../fleet}/types/intra_app_route_state.ts | 0 .../assets/illustration_integrations_darkmode.svg | 0 .../assets/illustration_integrations_lightmode.svg | 0 .../{ingest_manager => fleet}/public/index.ts | 8 ++++---- .../{ingest_manager => fleet}/public/plugin.ts | 12 ++++++------ .../scripts/dev_agent/index.js | 0 .../scripts/dev_agent/script.ts | 0 .../{ingest_manager => fleet}/scripts/readme.md | 0 .../server/collectors/agent_collectors.ts | 0 .../server/collectors/config_collectors.ts | 0 .../server/collectors/helpers.ts | 0 .../server/collectors/package_collectors.ts | 0 .../server/collectors/register.ts | 0 .../server/constants/index.ts | 0 .../server/errors/handlers.test.ts | 0 .../server/errors/handlers.ts | 0 .../server/errors/index.ts | 0 .../{ingest_manager => fleet}/server/index.ts | 0 .../server/integration_tests/router.test.ts | 4 ++-- .../{ingest_manager => fleet}/server/mocks.ts | 0 .../{ingest_manager => fleet}/server/plugin.ts | 0 .../server/routes/agent/acks_handlers.test.ts | 0 .../server/routes/agent/acks_handlers.ts | 0 .../server/routes/agent/actions_handlers.test.ts | 0 .../server/routes/agent/actions_handlers.ts | 0 .../server/routes/agent/handlers.ts | 0 .../server/routes/agent/index.ts | 0 .../server/routes/agent/unenroll_handler.ts | 0 .../server/routes/agent/upgrade_handler.ts | 0 .../server/routes/agent_policy/handlers.ts | 0 .../server/routes/agent_policy/index.ts | 0 .../server/routes/app/index.ts | 0 .../server/routes/data_streams/handlers.ts | 0 .../server/routes/data_streams/index.ts | 0 .../server/routes/enrollment_api_key/handler.ts | 0 .../server/routes/enrollment_api_key/index.ts | 0 .../server/routes/epm/handlers.ts | 0 .../server/routes/epm/index.ts | 0 .../server/routes/index.ts | 0 .../server/routes/install_script/index.ts | 0 .../server/routes/limited_concurrency.test.ts | 0 .../server/routes/limited_concurrency.ts | 0 .../server/routes/output/handler.ts | 0 .../server/routes/output/index.ts | 0 .../server/routes/package_policy/handlers.test.ts | 0 .../server/routes/package_policy/handlers.ts | 0 .../server/routes/package_policy/index.ts | 0 .../server/routes/settings/index.ts | 0 .../server/routes/setup/handlers.test.ts | 0 .../server/routes/setup/handlers.ts | 0 .../server/routes/setup/index.ts | 0 .../server/saved_objects/index.ts | 0 .../server/saved_objects/migrations/to_v7_10_0.ts | 0 .../server/services/agent_policy.test.ts | 0 .../server/services/agent_policy.ts | 0 .../server/services/agent_policy_update.ts | 0 .../server/services/agents/acks.test.ts | 0 .../server/services/agents/acks.ts | 0 .../server/services/agents/actions.test.ts | 0 .../server/services/agents/actions.ts | 0 .../server/services/agents/authenticate.test.ts | 0 .../server/services/agents/authenticate.ts | 0 .../server/services/agents/checkin/index.ts | 0 .../services/agents/checkin/rxjs_utils.test.ts | 0 .../server/services/agents/checkin/rxjs_utils.ts | 0 .../server/services/agents/checkin/state.ts | 0 .../agents/checkin/state_connected_agents.ts | 0 .../agents/checkin/state_new_actions.test.ts | 0 .../services/agents/checkin/state_new_actions.ts | 0 .../server/services/agents/crud.ts | 0 .../server/services/agents/enroll.test.ts | 0 .../server/services/agents/enroll.ts | 0 .../server/services/agents/events.ts | 0 .../server/services/agents/index.ts | 0 .../server/services/agents/reassign.ts | 0 .../server/services/agents/saved_objects.ts | 0 .../server/services/agents/setup.ts | 0 .../server/services/agents/status.test.ts | 0 .../server/services/agents/status.ts | 0 .../server/services/agents/unenroll.ts | 0 .../server/services/agents/update.ts | 0 .../server/services/agents/upgrade.ts | 0 .../server/services/api_keys/enrollment_api_key.ts | 0 .../server/services/api_keys/index.ts | 0 .../server/services/api_keys/security.ts | 0 .../server/services/app_context.ts | 0 .../server/services/config.ts | 0 .../server/services/epm/agent/agent.test.ts | 0 .../server/services/epm/agent/agent.ts | 0 .../server/services/epm/archive/cache.ts | 0 .../server/services/epm/archive/index.ts | 0 .../server/services/epm/archive/validation.ts | 0 .../services/epm/elasticsearch/ilm/install.ts | 0 .../server/services/epm/elasticsearch/index.test.ts | 0 .../server/services/epm/elasticsearch/index.ts | 0 .../epm/elasticsearch/ingest_pipeline/index.ts | 0 .../ingest_pipeline/ingest_pipelines.test.ts | 0 .../epm/elasticsearch/ingest_pipeline/install.ts | 0 .../epm/elasticsearch/ingest_pipeline/remove.ts | 0 .../tests/ingest_pipeline_template.json | 0 .../tests/ingest_pipelines/no_replacement.json | 0 .../tests/ingest_pipelines/no_replacement.yml | 0 .../tests/ingest_pipelines/real_input_beats.json | 0 .../tests/ingest_pipelines/real_input_beats.yml | 0 .../tests/ingest_pipelines/real_input_standard.json | 0 .../tests/ingest_pipelines/real_input_standard.yml | 0 .../tests/ingest_pipelines/real_output.json | 0 .../tests/ingest_pipelines/real_output.yml | 0 .../template/__snapshots__/template.test.ts.snap | 0 .../services/epm/elasticsearch/template/install.ts | 2 +- .../epm/elasticsearch/template/template.test.ts | 0 .../services/epm/elasticsearch/template/template.ts | 0 .../services/epm/elasticsearch/transform/common.ts | 0 .../services/epm/elasticsearch/transform/install.ts | 0 .../epm/elasticsearch/transform/remove.test.ts | 0 .../services/epm/elasticsearch/transform/remove.ts | 0 .../epm/elasticsearch/transform/transform.test.ts | 0 .../epm/fields/__snapshots__/field.test.ts.snap | 0 .../server/services/epm/fields/field.test.ts | 0 .../server/services/epm/fields/field.ts | 0 .../server/services/epm/fields/tests/base.yml | 0 .../services/epm/fields/tests/coredns.logs.yml | 0 .../server/services/epm/fields/tests/system.yml | 0 .../server/services/epm/kibana/assets/install.ts | 0 .../__snapshots__/install.test.ts.snap | 0 .../epm/kibana/index_pattern/install.test.ts | 0 .../services/epm/kibana/index_pattern/install.ts | 0 .../epm/kibana/index_pattern/tests/coredns.logs.yml | 0 .../kibana/index_pattern/tests/nginx.access.ecs.yml | 0 .../kibana/index_pattern/tests/nginx.error.ecs.yml | 0 .../epm/kibana/index_pattern/tests/nginx.fields.yml | 0 .../epm/kibana/index_pattern/tests/test_data.ts | 0 .../services/epm/packages/_install_package.test.ts | 0 .../services/epm/packages/_install_package.ts | 0 .../server/services/epm/packages/assets.test.ts | 0 .../server/services/epm/packages/assets.ts | 0 .../services/epm/packages/bulk_install_packages.ts | 0 .../ensure_installed_default_packages.test.ts | 0 .../server/services/epm/packages/get.ts | 0 .../services/epm/packages/get_install_type.test.ts | 0 .../server/services/epm/packages/index.ts | 0 .../server/services/epm/packages/install.ts | 0 .../server/services/epm/packages/remove.ts | 0 .../server/services/epm/registry/extract.ts | 0 .../server/services/epm/registry/index.test.ts | 0 .../server/services/epm/registry/index.ts | 0 .../server/services/epm/registry/proxy.test.ts | 0 .../server/services/epm/registry/proxy.ts | 0 .../server/services/epm/registry/registry_url.ts | 0 .../server/services/epm/registry/requests.test.ts | 0 .../server/services/epm/registry/requests.ts | 0 .../server/services/epm/registry/streams.ts | 0 .../server/services/es_index_pattern.ts | 0 .../server/services/index.ts | 0 .../server/services/install_script/index.ts | 0 .../install_script/install_templates/linux.ts | 0 .../install_script/install_templates/macos.ts | 0 .../install_script/install_templates/types.ts | 0 .../server/services/license.ts | 0 .../server/services/output.ts | 0 .../server/services/package_policy.test.ts | 0 .../server/services/package_policy.ts | 0 .../server/services/saved_object.test.ts | 0 .../server/services/saved_object.ts | 0 .../server/services/settings.ts | 0 .../server/services/setup.test.ts | 0 .../server/services/setup.ts | 0 .../server/services/setup_utils.test.ts | 0 .../server/services/setup_utils.ts | 0 .../server/types/index.tsx | 0 .../server/types/models/agent.ts | 0 .../server/types/models/agent_policy.ts | 0 .../server/types/models/enrollment_api_key.ts | 0 .../server/types/models/index.ts | 0 .../server/types/models/output.ts | 0 .../server/types/models/package_policy.ts | 0 .../server/types/rest_spec/agent.ts | 0 .../server/types/rest_spec/agent_policy.ts | 0 .../server/types/rest_spec/common.ts | 0 .../server/types/rest_spec/enrollment_api_key.ts | 0 .../server/types/rest_spec/epm.ts | 0 .../server/types/rest_spec/index.ts | 0 .../server/types/rest_spec/install_script.ts | 0 .../server/types/rest_spec/output.ts | 0 .../server/types/rest_spec/package_policy.ts | 0 .../server/types/rest_spec/settings.ts | 0 .../server/types/rest_spec/setup.ts | 0 .../public/application/app_context.tsx | 2 +- .../public/application/mount_management_section.ts | 2 +- x-pack/plugins/index_management/public/types.ts | 2 +- x-pack/plugins/security_solution/README.md | 2 +- .../common/endpoint/generate_data.ts | 9 +++------ .../security_solution/common/endpoint/index_data.ts | 2 +- .../endpoint/policy/migrations/to_v7_11.0.test.ts | 2 +- .../common/endpoint/policy/migrations/to_v7_11.0.ts | 2 +- .../common/endpoint/types/index.ts | 2 +- .../security_solution/public/app/home/setup.tsx | 2 +- .../public/common/hooks/endpoint/upgrade.ts | 2 +- .../common/mock/endpoint/dependencies_start_mock.ts | 5 +---- .../management/pages/endpoint_hosts/store/action.ts | 2 +- .../pages/endpoint_hosts/store/middleware.ts | 2 +- .../store/mock_endpoint_result_list.ts | 2 +- .../public/management/pages/endpoint_hosts/types.ts | 2 +- .../view/details/endpoint_details.tsx | 2 +- .../management/pages/endpoint_hosts/view/index.tsx | 2 +- .../pages/policy/store/policy_details/action.ts | 2 +- .../pages/policy/store/policy_list/action.ts | 5 +---- .../pages/policy/store/policy_list/index.test.ts | 2 +- .../pages/policy/store/policy_list/middleware.ts | 2 +- .../store/policy_list/services/ingest.test.ts | 2 +- .../policy/store/policy_list/services/ingest.ts | 2 +- .../policy/store/policy_list/test_mock_utils.ts | 2 +- .../public/management/pages/policy/types.ts | 2 +- .../configure_package_policy.tsx | 2 +- .../management/pages/policy/view/policy_list.tsx | 2 +- x-pack/plugins/security_solution/public/types.ts | 2 +- .../scripts/endpoint/resolver_generator_script.ts | 4 ++-- .../endpoint/endpoint_app_context_services.ts | 8 ++------ .../server/endpoint/ingest_integration.test.ts | 2 +- .../server/endpoint/ingest_integration.ts | 4 ++-- .../server/endpoint/lib/artifacts/mocks.ts | 4 ++-- .../security_solution/server/endpoint/mocks.ts | 4 ++-- .../routes/artifacts/download_exception_list.ts | 2 +- .../server/endpoint/routes/metadata/handlers.ts | 2 +- .../endpoint/routes/metadata/metadata.test.ts | 4 ++-- .../endpoint/routes/metadata/metadata_v1.test.ts | 4 ++-- .../routes/metadata/support/agent_status.test.ts | 6 +++--- .../routes/metadata/support/agent_status.ts | 6 +++--- .../routes/metadata/support/unenroll.test.ts | 4 ++-- .../endpoint/routes/metadata/support/unenroll.ts | 4 ++-- .../manifest_manager/manifest_manager.mock.ts | 4 ++-- .../manifest_manager/manifest_manager.test.ts | 2 +- .../artifacts/manifest_manager/manifest_manager.ts | 2 +- .../server/lib/hosts/elasticsearch_adapter.test.ts | 4 ++-- x-pack/plugins/security_solution/server/plugin.ts | 2 +- .../server/usage/endpoints/endpoint.mocks.ts | 6 +++--- .../server/usage/endpoints/endpoint.test.ts | 4 ++-- .../server/usage/endpoints/fleet_saved_objects.ts | 6 +++--- .../server/usage/endpoints/index.ts | 4 ++-- x-pack/test/common/services/ingest_manager.ts | 2 +- .../apis/epm/bulk_upgrade.ts | 2 +- .../apis/epm/install_remove_assets.ts | 2 +- .../apis/epm/install_update.ts | 2 +- .../apis/epm/package_install_complete.ts | 2 +- .../apis/epm/setup.ts | 2 +- .../apis/epm/template.ts | 2 +- .../apis/fleet/agents/upgrade.ts | 2 +- .../apps/endpoint/index.ts | 2 +- .../services/endpoint_policy.ts | 2 +- .../apis/index.ts | 2 +- 562 files changed, 112 insertions(+), 125 deletions(-) rename x-pack/plugins/{ingest_manager => fleet}/CHANGELOG.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/README.md (97%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/plugin.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/routes.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/constants/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/mocks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/bundled.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/bundled.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/headers/kbn_xsrf.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/kuery.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/page_index.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/parameters/page_size.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/access_api_key.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_event.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_metadata.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_status.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/agent_type.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/bulk_upgrade_agents.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/enrollment_api_key.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_agent_event.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_agent_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/new_package_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/package_info.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/package_policy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/search_result.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/components/schemas/upgrade_agent.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/entrypoint.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/README.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@delete.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@{agent_policy_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agent_status.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@bulk_upgrade.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@enroll.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@setup.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@acks.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@checkin.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@events.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@unenroll.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/agents@{agent_id}@upgrade.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/enrollment_api_keys.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/enrollment_api_keys@{key_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@categories.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@packages.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/epm@packages@{pkgkey}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/install@{os_type}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/package_policies.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/package_policies@{package_policy_id}.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/openapi/paths/setup.yaml (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/agent_status.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/decode_cloud_id.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/decode_cloud_id.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_kibana_config.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_kibana_config.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/full_agent_policy_to_yaml.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_agent_upgradeable.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_agent_upgradeable.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_diff_path_protocol.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_diff_path_protocol.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_valid_namespace.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/is_valid_namespace.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/license.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/limited_package.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_policies_to_agent_inputs.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_policies_to_agent_inputs.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_to_package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/package_to_package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/services/routes.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/models/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/app.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/fleet_setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/ingest_setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/install_script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/common/types/rest_spec/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/actions_and_events.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_acks.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_checkin.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_enroll.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_list.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/agents_unenroll.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api/epm.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api_integration_tests.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/api_keys.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/definitions.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/epm.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/fleet_agent_communication.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/fleet_agents_interactions_detailed.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/indexing_strategy.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_checkin.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_checkin.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_enroll.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/agent_enroll.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/saved_objects.mml (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/schema/saved_objects.png (100%) rename x-pack/plugins/{ingest_manager => fleet}/dev_docs/tracing.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/kibana.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/package.json (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/alpha_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/alpha_messaging.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/context_menu_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/enrollment_instructions/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/enrollment_instructions/manual/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/error.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_directory_header_link.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_directory_notice.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/home_integration/tutorial_module_notice.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/loading.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/package_icon.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/search_bar.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/components/settings_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/constants/page_paths.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_breadcrumbs.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_capabilities.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_config.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_core.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_debounce.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_deps.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_fleet_status.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_input.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_intra_app_state.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_kibana_link.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_kibana_version.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_license.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_link.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_package_icon_type.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_pagination.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/agents.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/app.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/data_stream.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/enrollment_api_keys.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/epm.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/outputs.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/settings.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/setup.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_request/use_request.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_sorting.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/hooks/use_url_params.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/default.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/with_header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/layouts/without_header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/actions_menu.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_copy_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_delete_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_form.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/agent_policy_yaml_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/confirm_deploy_modal.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/danger_eui_context_menu_item.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/linked_agent_count.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/components/package_policy_delete_provider.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_configure_package.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/step_select_package.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/create_package_policy_page/types.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/components/settings/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/use_agent_status.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/hooks/use_config.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/details_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/edit_package_policy_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/components/create_agent_policy.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/agent_policy/list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/actions_menu.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/agent_details.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/agent_events_table.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/helper.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/metadata_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/metadata_form.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/components/type_labels.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/hooks/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/hooks/use_agent.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_details_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_list_page/components/bulk_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/agent_list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/agent_policy_selection.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/managed_instructions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/standalone_instructions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_enrollment_flyout/steps.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_health.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_policy_package_badges.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_reassign_policy_flyout/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_unenroll_modal/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/agent_upgrade_modal/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/donut_chart.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/list_layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/components/loading.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/components/confirm_delete_modal.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/enrollment_token_list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/components/no_data_layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/enforce_security.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/invalid_license.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/error_pages/no_access.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager/sections/fleet => fleet/public/applications/fleet/sections/agents}/setup_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/list_page/components/data_stream_row_actions.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/data_stream/list_page/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/assets_facet_group.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/icon_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/icons.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/package_card.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/package_list_grid.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/release_badge.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/requirements.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/components/version.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/constants.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_links.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_local_search.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/hooks/use_package_install.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/confirm_package_install.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/confirm_package_uninstall.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/content.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/content_collapse.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/installation_button.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/layout.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/markdown_renderers.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/overview_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/package_policies_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/readme.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/screenshots.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/settings_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/detail/side_nav_links.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/category_facets.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/header.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/epm/screens/home/index.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/index.tsx (93%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/agent_policy_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/agent_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/datastream_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/integration_section.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/overview_panel.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/components/overview_stats.tsx (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/sections/overview/index.tsx (98%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/services/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/types/index.ts (100%) rename x-pack/plugins/{ingest_manager/public/applications/ingest_manager => fleet/public/applications/fleet}/types/intra_app_route_state.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/assets/illustration_integrations_darkmode.svg (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/assets/illustration_integrations_lightmode.svg (100%) rename x-pack/plugins/{ingest_manager => fleet}/public/index.ts (65%) rename x-pack/plugins/{ingest_manager => fleet}/public/plugin.ts (91%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/dev_agent/index.js (100%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/dev_agent/script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/scripts/readme.md (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/agent_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/config_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/helpers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/package_collectors.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/collectors/register.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/constants/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/errors/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/integration_tests/router.test.ts (97%) rename x-pack/plugins/{ingest_manager => fleet}/server/mocks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/plugin.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/acks_handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/acks_handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/actions_handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/actions_handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/unenroll_handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent/upgrade_handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent_policy/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/agent_policy/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/app/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/data_streams/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/data_streams/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/enrollment_api_key/handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/enrollment_api_key/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/epm/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/epm/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/install_script/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/limited_concurrency.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/limited_concurrency.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/output/handler.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/output/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/package_policy/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/settings/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/handlers.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/handlers.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/routes/setup/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/saved_objects/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/saved_objects/migrations/to_v7_10_0.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agent_policy_update.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/acks.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/acks.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/actions.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/actions.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/authenticate.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/authenticate.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/rxjs_utils.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/rxjs_utils.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_connected_agents.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_new_actions.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/checkin/state_new_actions.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/crud.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/enroll.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/enroll.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/events.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/reassign.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/saved_objects.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/status.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/status.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/unenroll.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/update.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/agents/upgrade.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/api_keys/security.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/app_context.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/config.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/agent/agent.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/agent/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/cache.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/archive/validation.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ilm/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/index.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/install.ts (98%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/template.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/template/template.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/remove.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/elasticsearch/transform/transform.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/__snapshots__/field.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/field.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/field.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/base.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/coredns.logs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/fields/tests/system.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/assets/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/install.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/kibana/index_pattern/tests/test_data.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/_install_package.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/_install_package.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/assets.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/assets.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/bulk_install_packages.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/ensure_installed_default_packages.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/get.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/get_install_type.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/install.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/packages/remove.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/extract.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/index.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/proxy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/proxy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/registry_url.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/requests.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/requests.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/epm/registry/streams.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/es_index_pattern.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/linux.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/macos.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/install_script/install_templates/types.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/license.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/package_policy.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/saved_object.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/saved_object.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup_utils.test.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/services/setup_utils.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/index.tsx (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/models/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/agent.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/agent_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/common.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/enrollment_api_key.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/epm.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/index.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/install_script.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/output.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/package_policy.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/settings.ts (100%) rename x-pack/plugins/{ingest_manager => fleet}/server/types/rest_spec/setup.ts (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 96670b5d5107b..2d70f6a97eed2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -136,7 +136,7 @@ # Observability UIs /x-pack/plugins/infra/ @elastic/logs-metrics-ui -/x-pack/plugins/ingest_manager/ @elastic/ingest-management +/x-pack/plugins/fleet/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/observability-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime @@ -380,7 +380,7 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Observability design /x-pack/plugins/apm/**/*.scss @elastic/observability-design /x-pack/plugins/infra/**/*.scss @elastic/observability-design -/x-pack/plugins/ingest_manager/**/*.scss @elastic/observability-design +/x-pack/plugins/fleet/**/*.scss @elastic/observability-design /x-pack/plugins/observability/**/*.scss @elastic/observability-design /x-pack/plugins/monitoring/**/*.scss @elastic/observability-design diff --git a/.github/paths-labeller.yml b/.github/paths-labeller.yml index 2e8529b4a7704..bd8427ea18d69 100644 --- a/.github/paths-labeller.yml +++ b/.github/paths-labeller.yml @@ -11,7 +11,7 @@ - "Team:apm": - "x-pack/plugins/apm/**/*.*" - "Team:Ingest Management": - - "x-pack/plugins/ingest_manager/**/*.*" + - "x-pack/plugins/fleet/**/*.*" - "x-pack/test/api_integration/apis/fleet/**/*.*" - "x-pack/test/epm_api_integration/**/*.*" - "Team:uptime": diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 3c62c1fbca982..e89b6d86361c7 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -413,7 +413,7 @@ Index Management by running this series of requests in Console: the infrastructure monitoring use-case within Kibana. -|{kib-repo}blob/{branch}/x-pack/plugins/ingest_manager/README.md[ingestManager] +|{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[ingestManager] |Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 3b1e4faf80bce..2be68b797ba5f 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -28,7 +28,7 @@ "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", - "xpack.fleet": "plugins/ingest_manager", + "xpack.fleet": "plugins/fleet", "xpack.ingestPipelines": "plugins/ingest_pipelines", "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", diff --git a/x-pack/plugins/ingest_manager/CHANGELOG.md b/x-pack/plugins/fleet/CHANGELOG.md similarity index 100% rename from x-pack/plugins/ingest_manager/CHANGELOG.md rename to x-pack/plugins/fleet/CHANGELOG.md diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/fleet/README.md similarity index 97% rename from x-pack/plugins/ingest_manager/README.md rename to x-pack/plugins/fleet/README.md index ade5985782c89..78ac2e3bdfdbe 100644 --- a/x-pack/plugins/ingest_manager/README.md +++ b/x-pack/plugins/fleet/README.md @@ -2,7 +2,7 @@ ## Plugin -- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27) +- The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/common/types/index.ts#L9-L27) - Adding `xpack.fleet.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) - Adding `--xpack.fleet.agents.enabled=false` will disable the Fleet API & UI - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/fleet/common/constants/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/agent.ts rename to x-pack/plugins/fleet/common/constants/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/agent_policy.ts rename to x-pack/plugins/fleet/common/constants/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts b/x-pack/plugins/fleet/common/constants/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/constants/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/epm.ts rename to x-pack/plugins/fleet/common/constants/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/index.ts rename to x-pack/plugins/fleet/common/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/output.ts b/x-pack/plugins/fleet/common/constants/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/output.ts rename to x-pack/plugins/fleet/common/constants/output.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/package_policy.ts b/x-pack/plugins/fleet/common/constants/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/package_policy.ts rename to x-pack/plugins/fleet/common/constants/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/plugin.ts rename to x-pack/plugins/fleet/common/constants/plugin.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/routes.ts rename to x-pack/plugins/fleet/common/constants/routes.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/settings.ts b/x-pack/plugins/fleet/common/constants/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/constants/settings.ts rename to x-pack/plugins/fleet/common/constants/settings.ts diff --git a/x-pack/plugins/ingest_manager/common/index.ts b/x-pack/plugins/fleet/common/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/index.ts rename to x-pack/plugins/fleet/common/index.ts diff --git a/x-pack/plugins/ingest_manager/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/mocks.ts rename to x-pack/plugins/fleet/common/mocks.ts diff --git a/x-pack/plugins/ingest_manager/common/openapi/README.md b/x-pack/plugins/fleet/common/openapi/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/README.md rename to x-pack/plugins/fleet/common/openapi/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/bundled.json rename to x-pack/plugins/fleet/common/openapi/bundled.json diff --git a/x-pack/plugins/ingest_manager/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/bundled.yaml rename to x-pack/plugins/fleet/common/openapi/bundled.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/README.md b/x-pack/plugins/fleet/common/openapi/components/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/README.md rename to x-pack/plugins/fleet/common/openapi/components/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/headers/kbn_xsrf.yaml b/x-pack/plugins/fleet/common/openapi/components/headers/kbn_xsrf.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/headers/kbn_xsrf.yaml rename to x-pack/plugins/fleet/common/openapi/components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/kuery.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/kuery.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/kuery.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/kuery.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_index.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/page_index.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_index.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/page_index.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_size.yaml b/x-pack/plugins/fleet/common/openapi/components/parameters/page_size.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/parameters/page_size.yaml rename to x-pack/plugins/fleet/common/openapi/components/parameters/page_size.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/access_api_key.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/access_api_key.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/access_api_key.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/access_api_key.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_event.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_metadata.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_metadata.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_metadata.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_metadata.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_status.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_status.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_status.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_type.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_type.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/agent_type.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/agent_type.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/bulk_upgrade_agents.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/bulk_upgrade_agents.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/bulk_upgrade_agents.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/bulk_upgrade_agents.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/enrollment_api_key.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/enrollment_api_key.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/enrollment_api_key.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_event.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_agent_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/new_package_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/new_package_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_info.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_info.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_info.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/package_info.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/package_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/package_policy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/search_result.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/search_result.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/search_result.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/search_result.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/components/schemas/upgrade_agent.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/upgrade_agent.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/components/schemas/upgrade_agent.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/upgrade_agent.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/entrypoint.yaml rename to x-pack/plugins/fleet/common/openapi/entrypoint.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/README.md b/x-pack/plugins/fleet/common/openapi/paths/README.md similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/README.md rename to x-pack/plugins/fleet/common/openapi/paths/README.md diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@delete.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@delete.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@delete.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agent_status.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agent_status.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agent_status.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@bulk_upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@bulk_upgrade.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@bulk_upgrade.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@enroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@enroll.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@enroll.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@setup.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@setup.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@acks.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@acks.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@acks.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@acks.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@checkin.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@checkin.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@checkin.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@checkin.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@events.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@events.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@events.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@events.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@unenroll.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@upgrade.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/agents@{agent_id}@upgrade.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@upgrade.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys.yaml rename to x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys@{key_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/enrollment_api_keys@{key_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/enrollment_api_keys@{key_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@categories.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@categories.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@categories.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages@{pkgkey}.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/epm@packages@{pkgkey}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/epm@packages@{pkgkey}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/install@{os_type}.yaml b/x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/install@{os_type}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/install@{os_type}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/package_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/package_policies.yaml rename to x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/package_policies@{package_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/package_policies@{package_policy_id}.yaml rename to x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml diff --git a/x-pack/plugins/ingest_manager/common/openapi/paths/setup.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup.yaml similarity index 100% rename from x-pack/plugins/ingest_manager/common/openapi/paths/setup.yaml rename to x-pack/plugins/fleet/common/openapi/paths/setup.yaml diff --git a/x-pack/plugins/ingest_manager/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/agent_status.ts rename to x-pack/plugins/fleet/common/services/agent_status.ts diff --git a/x-pack/plugins/ingest_manager/common/services/decode_cloud_id.test.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/decode_cloud_id.test.ts rename to x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/decode_cloud_id.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/decode_cloud_id.ts rename to x-pack/plugins/fleet/common/services/decode_cloud_id.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.test.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.test.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_kibana_config.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_kibana_config.ts diff --git a/x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/full_agent_policy_to_yaml.ts rename to x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/index.ts rename to x-pack/plugins/fleet/common/services/index.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.test.ts rename to x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_agent_upgradeable.ts rename to x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.test.ts rename to x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_diff_path_protocol.ts rename to x-pack/plugins/fleet/common/services/is_diff_path_protocol.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_valid_namespace.test.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_valid_namespace.test.ts rename to x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/is_valid_namespace.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/is_valid_namespace.ts rename to x-pack/plugins/fleet/common/services/is_valid_namespace.ts diff --git a/x-pack/plugins/ingest_manager/common/services/license.ts b/x-pack/plugins/fleet/common/services/license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/license.ts rename to x-pack/plugins/fleet/common/services/license.ts diff --git a/x-pack/plugins/ingest_manager/common/services/limited_package.ts b/x-pack/plugins/fleet/common/services/limited_package.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/limited_package.ts rename to x-pack/plugins/fleet/common/services/limited_package.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.test.ts rename to x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_policies_to_agent_inputs.ts rename to x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts rename to x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts rename to x-pack/plugins/fleet/common/services/package_to_package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/services/routes.ts rename to x-pack/plugins/fleet/common/services/routes.ts diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/index.ts rename to x-pack/plugins/fleet/common/types/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/agent.ts rename to x-pack/plugins/fleet/common/types/models/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/agent_policy.ts rename to x-pack/plugins/fleet/common/types/models/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/data_stream.ts b/x-pack/plugins/fleet/common/types/models/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/data_stream.ts rename to x-pack/plugins/fleet/common/types/models/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/epm.ts rename to x-pack/plugins/fleet/common/types/models/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/index.ts b/x-pack/plugins/fleet/common/types/models/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/index.ts rename to x-pack/plugins/fleet/common/types/models/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/output.ts rename to x-pack/plugins/fleet/common/types/models/output.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/package_policy.ts rename to x-pack/plugins/fleet/common/types/models/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/models/settings.ts rename to x-pack/plugins/fleet/common/types/models/settings.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts rename to x-pack/plugins/fleet/common/types/rest_spec/agent.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts rename to x-pack/plugins/fleet/common/types/rest_spec/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/app.ts b/x-pack/plugins/fleet/common/types/rest_spec/app.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/app.ts rename to x-pack/plugins/fleet/common/types/rest_spec/app.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts b/x-pack/plugins/fleet/common/types/rest_spec/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts rename to x-pack/plugins/fleet/common/types/rest_spec/common.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/data_stream.ts b/x-pack/plugins/fleet/common/types/rest_spec/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/data_stream.ts rename to x-pack/plugins/fleet/common/types/rest_spec/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts rename to x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts rename to x-pack/plugins/fleet/common/types/rest_spec/epm.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts rename to x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts b/x-pack/plugins/fleet/common/types/rest_spec/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts rename to x-pack/plugins/fleet/common/types/rest_spec/index.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/ingest_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/ingest_setup.ts rename to x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/common/types/rest_spec/install_script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/install_script.ts rename to x-pack/plugins/fleet/common/types/rest_spec/install_script.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts rename to x-pack/plugins/fleet/common/types/rest_spec/output.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/common/types/rest_spec/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts rename to x-pack/plugins/fleet/common/types/rest_spec/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts b/x-pack/plugins/fleet/common/types/rest_spec/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts rename to x-pack/plugins/fleet/common/types/rest_spec/settings.ts diff --git a/x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md b/x-pack/plugins/fleet/dev_docs/actions_and_events.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/actions_and_events.md rename to x-pack/plugins/fleet/dev_docs/actions_and_events.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_acks.md b/x-pack/plugins/fleet/dev_docs/api/agents_acks.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_acks.md rename to x-pack/plugins/fleet/dev_docs/api/agents_acks.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_checkin.md b/x-pack/plugins/fleet/dev_docs/api/agents_checkin.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_checkin.md rename to x-pack/plugins/fleet/dev_docs/api/agents_checkin.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md b/x-pack/plugins/fleet/dev_docs/api/agents_enroll.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_enroll.md rename to x-pack/plugins/fleet/dev_docs/api/agents_enroll.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md b/x-pack/plugins/fleet/dev_docs/api/agents_list.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md rename to x-pack/plugins/fleet/dev_docs/api/agents_list.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_unenroll.md b/x-pack/plugins/fleet/dev_docs/api/agents_unenroll.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/agents_unenroll.md rename to x-pack/plugins/fleet/dev_docs/api/agents_unenroll.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/epm.md b/x-pack/plugins/fleet/dev_docs/api/epm.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api/epm.md rename to x-pack/plugins/fleet/dev_docs/api/epm.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api_integration_tests.md b/x-pack/plugins/fleet/dev_docs/api_integration_tests.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api_integration_tests.md rename to x-pack/plugins/fleet/dev_docs/api_integration_tests.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/api_keys.md b/x-pack/plugins/fleet/dev_docs/api_keys.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/api_keys.md rename to x-pack/plugins/fleet/dev_docs/api_keys.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/definitions.md b/x-pack/plugins/fleet/dev_docs/definitions.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/definitions.md rename to x-pack/plugins/fleet/dev_docs/definitions.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/epm.md b/x-pack/plugins/fleet/dev_docs/epm.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/epm.md rename to x-pack/plugins/fleet/dev_docs/epm.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/fleet_agent_communication.md b/x-pack/plugins/fleet/dev_docs/fleet_agent_communication.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/fleet_agent_communication.md rename to x-pack/plugins/fleet/dev_docs/fleet_agent_communication.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md b/x-pack/plugins/fleet/dev_docs/fleet_agents_interactions_detailed.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/fleet_agents_interactions_detailed.md rename to x-pack/plugins/fleet/dev_docs/fleet_agents_interactions_detailed.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/indexing_strategy.md b/x-pack/plugins/fleet/dev_docs/indexing_strategy.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/indexing_strategy.md rename to x-pack/plugins/fleet/dev_docs/indexing_strategy.md diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml b/x-pack/plugins/fleet/dev_docs/schema/agent_checkin.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.mml rename to x-pack/plugins/fleet/dev_docs/schema/agent_checkin.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.png b/x-pack/plugins/fleet/dev_docs/schema/agent_checkin.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_checkin.png rename to x-pack/plugins/fleet/dev_docs/schema/agent_checkin.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.mml b/x-pack/plugins/fleet/dev_docs/schema/agent_enroll.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.mml rename to x-pack/plugins/fleet/dev_docs/schema/agent_enroll.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.png b/x-pack/plugins/fleet/dev_docs/schema/agent_enroll.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/agent_enroll.png rename to x-pack/plugins/fleet/dev_docs/schema/agent_enroll.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml b/x-pack/plugins/fleet/dev_docs/schema/saved_objects.mml similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml rename to x-pack/plugins/fleet/dev_docs/schema/saved_objects.mml diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.png b/x-pack/plugins/fleet/dev_docs/schema/saved_objects.png similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.png rename to x-pack/plugins/fleet/dev_docs/schema/saved_objects.png diff --git a/x-pack/plugins/ingest_manager/dev_docs/tracing.md b/x-pack/plugins/fleet/dev_docs/tracing.md similarity index 100% rename from x-pack/plugins/ingest_manager/dev_docs/tracing.md rename to x-pack/plugins/fleet/dev_docs/tracing.md diff --git a/x-pack/plugins/ingest_manager/kibana.json b/x-pack/plugins/fleet/kibana.json similarity index 100% rename from x-pack/plugins/ingest_manager/kibana.json rename to x-pack/plugins/fleet/kibana.json diff --git a/x-pack/plugins/ingest_manager/package.json b/x-pack/plugins/fleet/package.json similarity index 100% rename from x-pack/plugins/ingest_manager/package.json rename to x-pack/plugins/fleet/package.json diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/alpha_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/alpha_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_messaging.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/alpha_messaging.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/alpha_messaging.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/alpha_messaging.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/context_menu_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/context_menu_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/enrollment_instructions/manual/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/error.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/error.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/error.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/error.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_header_link.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_header_link.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_header_link.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_header_link.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_notice.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_directory_notice.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_directory_notice.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/home_integration/tutorial_module_notice.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/loading.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/package_icon.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/package_icon.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/page_paths.ts rename to x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_breadcrumbs.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_capabilities.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_core.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_core.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_debounce.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_debounce.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_fleet_status.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_fleet_status.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_intra_app_state.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_intra_app_state.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_link.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_link.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_link.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_link.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_version.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_version.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_kibana_version.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_kibana_version.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_license.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_link.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_link.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_package_icon_type.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_package_icon_type.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_package_icon_type.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_package_icon_type.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_pagination.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_pagination.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agents.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/agents.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/app.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/app.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/app.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/app.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/data_stream.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/data_stream.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/data_stream.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/data_stream.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/enrollment_api_keys.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/enrollment_api_keys.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/enrollment_api_keys.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/enrollment_api_keys.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/epm.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/outputs.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/outputs.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/package_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/settings.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/setup.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/setup.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/setup.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_request/use_request.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_sorting.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_sorting.tsx rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_sorting.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_url_params.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_url_params.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_url_params.ts rename to x-pack/plugins/fleet/public/applications/fleet/hooks/use_url_params.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/with_header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/without_header.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/without_header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/without_header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/layouts/without_header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx index 55872aee5c3d3..4ded4b7786c8f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx @@ -9,7 +9,7 @@ import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import { AgentPolicy } from '../../../types'; import { useCapabilities } from '../../../hooks'; import { ContextMenuActions } from '../../../components'; -import { AgentEnrollmentFlyout } from '../../fleet/components'; +import { AgentEnrollmentFlyout } from '../../agents/components'; import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout'; import { AgentPolicyCopyProvider } from './agent_policy_copy_provider'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_form.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/confirm_deploy_modal.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/danger_eui_context_menu_item.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/danger_eui_context_menu_item.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/danger_eui_context_menu_item.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/danger_eui_context_menu_item.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/linked_agent_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/linked_agent_count.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/linked_agent_count.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_select_package.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/types.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/types.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_agent_status.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_agent_status.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_agent_status.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/hooks/use_config.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/hooks/use_config.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_details.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_details.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_events_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_events_table.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/agent_events_table.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_events_table.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/helper.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/helper.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/helper.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/helper.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_form.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/metadata_form.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/metadata_form.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/type_labels.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/type_labels.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/type_labels.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/type_labels.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/use_agent.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/use_agent.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/hooks/use_agent.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/hooks/use_agent.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/agent_policy_selection.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/agent_policy_selection.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/agent_policy_selection.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/managed_instructions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/managed_instructions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/standalone_instructions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/steps.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/steps.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_health.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_policy_package_badges.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_modal/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_upgrade_modal/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/donut_chart.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/donut_chart.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/donut_chart.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/donut_chart.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/list_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/list_layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/list_layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/loading.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/loading.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/loading.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/loading.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/components/no_data_layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/components/no_data_layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/components/no_data_layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/components/no_data_layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/enforce_security.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/enforce_security.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/enforce_security.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/enforce_security.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/invalid_license.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/invalid_license.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/invalid_license.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/invalid_license.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/no_access.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/error_pages/no_access.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/agents/setup_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/components/data_stream_row_actions.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/components/data_stream_row_actions.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/data_stream/list_page/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/assets_facet_group.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icon_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icons.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icons.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/icons.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_card.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/release_badge.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/release_badge.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/release_badge.ts rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/release_badge.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/requirements.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/version.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_links.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_local_search.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_local_search.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/hooks/use_package_install.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_install.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_install.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_uninstall.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_uninstall.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/confirm_package_uninstall.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content_collapse.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content_collapse.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/installation_button.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/installation_button.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/layout.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/layout.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/markdown_renderers.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/markdown_renderers.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/overview_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/package_policies_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/package_policies_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/readme.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/readme.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/screenshots.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/screenshots.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/side_nav_links.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/side_nav_links.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/category_facets.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/header.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/header.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/home/index.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx similarity index 93% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx index 3a0f2c3ea8d58..7642c1b3c8af2 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/index.tsx @@ -7,6 +7,6 @@ export { IngestManagerOverview } from './overview'; export { EPMApp } from './epm'; export { AgentPolicyApp } from './agent_policy'; export { DataStreamApp } from './data_stream'; -export { FleetApp } from './fleet'; +export { FleetApp } from './agents'; export type Section = 'overview' | 'epm' | 'agent_policy' | 'fleet' | 'data_stream'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx index c49f99cfe8a04..6d26328cda4b9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_policy_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx @@ -18,7 +18,7 @@ import { OverviewStats } from './overview_stats'; import { SO_SEARCH_LIMIT } from '../../../constants'; import { useLink, useGetPackagePolicies } from '../../../hooks'; import { AgentPolicy } from '../../../types'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = ({ agentPolicies, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx index 5fbdf62d138d4..89d869c97621e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/agent_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetAgentStatus } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewAgentSection = () => { const { getHref } = useLink(); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx index 3b645f7aa9d66..58f84e8671385 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/datastream_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/datastream_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetDataStreams, useStartDeps } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; export const OverviewDatastreamSection: React.FC = () => { const { getHref } = useLink(); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx index 3f13b65a160d8..d69ec1f2aa1dc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/integration_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/integration_section.tsx @@ -16,7 +16,7 @@ import { import { OverviewPanel } from './overview_panel'; import { OverviewStats } from './overview_stats'; import { useLink, useGetPackages } from '../../../hooks'; -import { Loading } from '../../fleet/components'; +import { Loading } from '../../agents/components'; import { installationStatuses } from '../../../../../../common/constants'; export const OverviewIntegrationSection: React.FC = () => { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_panel.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_panel.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_stats.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/components/overview_stats.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/overview_stats.tsx diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx similarity index 98% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx rename to x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx index c997caa90c653..1254d5db04d19 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/index.tsx @@ -17,7 +17,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { WithHeaderLayout } from '../../layouts'; import { useGetAgentPolicies, useBreadcrumbs } from '../../hooks'; -import { AgentEnrollmentFlyout } from '../fleet/components'; +import { AgentEnrollmentFlyout } from '../agents/components'; import { OverviewAgentSection } from './components/agent_section'; import { OverviewPolicySection } from './components/agent_policy_section'; import { OverviewIntegrationSection } from './components/integration_section'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/fleet/public/applications/fleet/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/services/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts rename to x-pack/plugins/fleet/public/applications/fleet/types/index.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts b/x-pack/plugins/fleet/public/applications/fleet/types/intra_app_route_state.ts similarity index 100% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts rename to x-pack/plugins/fleet/public/applications/fleet/types/intra_app_route_state.ts diff --git a/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg b/x-pack/plugins/fleet/public/assets/illustration_integrations_darkmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/assets/illustration_integrations_darkmode.svg rename to x-pack/plugins/fleet/public/assets/illustration_integrations_darkmode.svg diff --git a/x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg b/x-pack/plugins/fleet/public/assets/illustration_integrations_lightmode.svg similarity index 100% rename from x-pack/plugins/ingest_manager/public/assets/illustration_integrations_lightmode.svg rename to x-pack/plugins/fleet/public/assets/illustration_integrations_lightmode.svg diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/fleet/public/index.ts similarity index 65% rename from x-pack/plugins/ingest_manager/public/index.ts rename to x-pack/plugins/fleet/public/index.ts index a8a810b68735a..f974a8c3d3cc8 100644 --- a/x-pack/plugins/ingest_manager/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -16,9 +16,9 @@ export { CustomConfigurePackagePolicyContent, CustomConfigurePackagePolicyProps, registerPackagePolicyComponent, -} from './applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +} from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; -export type { NewPackagePolicy } from './applications/ingest_manager/types'; -export * from './applications/ingest_manager/types/intra_app_route_state'; +export type { NewPackagePolicy } from './applications/fleet/types'; +export * from './applications/fleet/types/intra_app_route_state'; -export { pagePathGetters } from './applications/ingest_manager/constants'; +export { pagePathGetters } from './applications/fleet/constants'; diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts similarity index 91% rename from x-pack/plugins/ingest_manager/public/plugin.ts rename to x-pack/plugins/fleet/public/plugin.ts index 6847a39819e8e..2e7cbb9cb86ab 100644 --- a/x-pack/plugins/ingest_manager/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -19,18 +19,18 @@ import { } from '../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../licensing/public'; import { PLUGIN_ID, CheckPermissionsResponse, PostIngestSetupResponse } from '../common'; -import { BASE_PATH } from './applications/ingest_manager/constants'; +import { BASE_PATH } from './applications/fleet/constants'; import { IngestManagerConfigType } from '../common/types'; import { setupRouteService, appRoutesService } from '../common'; -import { licenseService } from './applications/ingest_manager/hooks/use_license'; -import { setHttpClient } from './applications/ingest_manager/hooks/use_request/use_request'; +import { licenseService } from './applications/fleet/hooks/use_license'; +import { setHttpClient } from './applications/fleet/hooks/use_request/use_request'; import { TutorialDirectoryNotice, TutorialDirectoryHeaderLink, TutorialModuleNotice, -} from './applications/ingest_manager/components/home_integration'; -import { registerPackagePolicyComponent } from './applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +} from './applications/fleet/components/home_integration'; +import { registerPackagePolicyComponent } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; export { IngestManagerConfigType } from '../common/types'; @@ -91,7 +91,7 @@ export class IngestManagerPlugin IngestManagerStartDeps, IngestManagerStart ]; - const { renderApp, teardownIngestManager } = await import('./applications/ingest_manager'); + const { renderApp, teardownIngestManager } = await import('./applications/fleet/'); const unmount = renderApp(coreStart, params, deps, startDeps, config, kibanaVersion); return () => { diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/index.js b/x-pack/plugins/fleet/scripts/dev_agent/index.js similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/dev_agent/index.js rename to x-pack/plugins/fleet/scripts/dev_agent/index.js diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/fleet/scripts/dev_agent/script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts rename to x-pack/plugins/fleet/scripts/dev_agent/script.ts diff --git a/x-pack/plugins/ingest_manager/scripts/readme.md b/x-pack/plugins/fleet/scripts/readme.md similarity index 100% rename from x-pack/plugins/ingest_manager/scripts/readme.md rename to x-pack/plugins/fleet/scripts/readme.md diff --git a/x-pack/plugins/ingest_manager/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/agent_collectors.ts rename to x-pack/plugins/fleet/server/collectors/agent_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts b/x-pack/plugins/fleet/server/collectors/config_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts rename to x-pack/plugins/fleet/server/collectors/config_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/helpers.ts b/x-pack/plugins/fleet/server/collectors/helpers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/helpers.ts rename to x-pack/plugins/fleet/server/collectors/helpers.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/package_collectors.ts b/x-pack/plugins/fleet/server/collectors/package_collectors.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/package_collectors.ts rename to x-pack/plugins/fleet/server/collectors/package_collectors.ts diff --git a/x-pack/plugins/ingest_manager/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/collectors/register.ts rename to x-pack/plugins/fleet/server/collectors/register.ts diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/constants/index.ts rename to x-pack/plugins/fleet/server/constants/index.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.test.ts b/x-pack/plugins/fleet/server/errors/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/handlers.test.ts rename to x-pack/plugins/fleet/server/errors/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/handlers.ts rename to x-pack/plugins/fleet/server/errors/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/errors/index.ts rename to x-pack/plugins/fleet/server/errors/index.ts diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/fleet/server/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/index.ts rename to x-pack/plugins/fleet/server/index.ts diff --git a/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts b/x-pack/plugins/fleet/server/integration_tests/router.test.ts similarity index 97% rename from x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts rename to x-pack/plugins/fleet/server/integration_tests/router.test.ts index 46b10ff381a60..c110ad23b6191 100644 --- a/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/router.test.ts @@ -21,7 +21,7 @@ function createXPackRoot(config: {} = {}) { scanDirs: [], paths: [ resolve(__dirname, '../../../../../x-pack/plugins/encrypted_saved_objects'), - resolve(__dirname, '../../../../../x-pack/plugins/ingest_manager'), + resolve(__dirname, '../../../../../x-pack/plugins/fleet'), resolve(__dirname, '../../../../../x-pack/plugins/licensing'), ], }, @@ -94,7 +94,7 @@ describe('ingestManager', () => { // For now, only the manager routes (/agent_policies & /package_policies) are added // EPM and ingest will be conditionally added when we enable these lines - // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/ingest_manager/server/plugin.ts#L84 + // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/fleet/server/plugin.ts#L84 // adding tests to confirm the Fleet & EPM routes are never added describe('manager and EPM; no Fleet', () => { diff --git a/x-pack/plugins/ingest_manager/server/mocks.ts b/x-pack/plugins/fleet/server/mocks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/mocks.ts rename to x-pack/plugins/fleet/server/mocks.ts diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/plugin.ts rename to x-pack/plugins/fleet/server/plugin.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts rename to x-pack/plugins/fleet/server/routes/agent/acks_handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/acks_handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts rename to x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts rename to x-pack/plugins/fleet/server/routes/agent/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/index.ts rename to x-pack/plugins/fleet/server/routes/agent/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts rename to x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts rename to x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts rename to x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/index.ts b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/agent_policy/index.ts rename to x-pack/plugins/fleet/server/routes/agent_policy/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/app/index.ts rename to x-pack/plugins/fleet/server/routes/app/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/data_streams/handlers.ts rename to x-pack/plugins/fleet/server/routes/data_streams/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/data_streams/index.ts b/x-pack/plugins/fleet/server/routes/data_streams/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/data_streams/index.ts rename to x-pack/plugins/fleet/server/routes/data_streams/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/handler.ts rename to x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/enrollment_api_key/index.ts rename to x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts rename to x-pack/plugins/fleet/server/routes/epm/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/epm/index.ts rename to x-pack/plugins/fleet/server/routes/epm/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/index.ts rename to x-pack/plugins/fleet/server/routes/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/install_script/index.ts b/x-pack/plugins/fleet/server/routes/install_script/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/install_script/index.ts rename to x-pack/plugins/fleet/server/routes/install_script/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts rename to x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts rename to x-pack/plugins/fleet/server/routes/limited_concurrency.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/output/handler.ts rename to x-pack/plugins/fleet/server/routes/output/handler.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/output/index.ts b/x-pack/plugins/fleet/server/routes/output/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/output/index.ts rename to x-pack/plugins/fleet/server/routes/output/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts rename to x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts rename to x-pack/plugins/fleet/server/routes/package_policy/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/index.ts b/x-pack/plugins/fleet/server/routes/package_policy/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/package_policy/index.ts rename to x-pack/plugins/fleet/server/routes/package_policy/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/settings/index.ts rename to x-pack/plugins/fleet/server/routes/settings/index.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/handlers.test.ts rename to x-pack/plugins/fleet/server/routes/setup/handlers.test.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts rename to x-pack/plugins/fleet/server/routes/setup/handlers.ts diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts b/x-pack/plugins/fleet/server/routes/setup/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/routes/setup/index.ts rename to x-pack/plugins/fleet/server/routes/setup/index.ts diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/saved_objects/index.ts rename to x-pack/plugins/fleet/server/saved_objects/index.ts diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/saved_objects/migrations/to_v7_10_0.ts rename to x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_10_0.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy.test.ts rename to x-pack/plugins/fleet/server/services/agent_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy.ts rename to x-pack/plugins/fleet/server/services/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agent_policy_update.ts rename to x-pack/plugins/fleet/server/services/agent_policy_update.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/fleet/server/services/agents/acks.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts rename to x-pack/plugins/fleet/server/services/agents/acks.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/fleet/server/services/agents/acks.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/acks.ts rename to x-pack/plugins/fleet/server/services/agents/acks.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts b/x-pack/plugins/fleet/server/services/agents/actions.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts rename to x-pack/plugins/fleet/server/services/agents/actions.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/actions.ts rename to x-pack/plugins/fleet/server/services/agents/actions.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts rename to x-pack/plugins/fleet/server/services/agents/authenticate.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts b/x-pack/plugins/fleet/server/services/agents/authenticate.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts rename to x-pack/plugins/fleet/server/services/agents/authenticate.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts b/x-pack/plugins/fleet/server/services/agents/checkin/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/index.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts b/x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts b/x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/rxjs_utils.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_connected_agents.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_connected_agents.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_connected_agents.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.test.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.test.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts rename to x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/crud.ts rename to x-pack/plugins/fleet/server/services/agents/crud.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts b/x-pack/plugins/fleet/server/services/agents/enroll.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts rename to x-pack/plugins/fleet/server/services/agents/enroll.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/fleet/server/services/agents/enroll.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/enroll.ts rename to x-pack/plugins/fleet/server/services/agents/enroll.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/events.ts b/x-pack/plugins/fleet/server/services/agents/events.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/events.ts rename to x-pack/plugins/fleet/server/services/agents/events.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/fleet/server/services/agents/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/index.ts rename to x-pack/plugins/fleet/server/services/agents/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/reassign.ts rename to x-pack/plugins/fleet/server/services/agents/reassign.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/fleet/server/services/agents/saved_objects.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts rename to x-pack/plugins/fleet/server/services/agents/saved_objects.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/setup.ts b/x-pack/plugins/fleet/server/services/agents/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/setup.ts rename to x-pack/plugins/fleet/server/services/agents/setup.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.test.ts b/x-pack/plugins/fleet/server/services/agents/status.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/status.test.ts rename to x-pack/plugins/fleet/server/services/agents/status.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/status.ts rename to x-pack/plugins/fleet/server/services/agents/status.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts rename to x-pack/plugins/fleet/server/services/agents/unenroll.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/update.ts b/x-pack/plugins/fleet/server/services/agents/update.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/update.ts rename to x-pack/plugins/fleet/server/services/agents/update.ts diff --git a/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts rename to x-pack/plugins/fleet/server/services/agents/upgrade.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/index.ts b/x-pack/plugins/fleet/server/services/api_keys/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/index.ts rename to x-pack/plugins/fleet/server/services/api_keys/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/security.ts b/x-pack/plugins/fleet/server/services/api_keys/security.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/api_keys/security.ts rename to x-pack/plugins/fleet/server/services/api_keys/security.ts diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/app_context.ts rename to x-pack/plugins/fleet/server/services/app_context.ts diff --git a/x-pack/plugins/ingest_manager/server/services/config.ts b/x-pack/plugins/fleet/server/services/config.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/config.ts rename to x-pack/plugins/fleet/server/services/config.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/agent/agent.test.ts rename to x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts rename to x-pack/plugins/fleet/server/services/epm/agent/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts rename to x-pack/plugins/fleet/server/services/epm/archive/cache.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts rename to x-pack/plugins/fleet/server/services/epm/archive/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts rename to x-pack/plugins/fleet/server/services/epm/archive/validation.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ilm/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/index.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/index.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/index.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/remove.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipeline_template.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/no_replacement.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_beats.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_input_standard.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.json diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/tests/ingest_pipelines/real_output.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts similarity index 98% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 6a4d1ca0e1d0a..25d412b685904 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -219,7 +219,7 @@ function buildComponentTemplates(registryElasticsearch: RegistryElasticsearch | // temporary change until https://github.com/elastic/elasticsearch/issues/58956 is resolved // hopefully we'll be able to remove the entire properties section once that issue is resolved properties: { - // if the timestamp_field changes here: https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts#L309 + // if the timestamp_field changes here: https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts#L309 // we'll need to update this as well '@timestamp': { type: 'date', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/common.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/common.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/common.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/install.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/remove.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/transform/transform.test.ts rename to x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/__snapshots__/field.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/__snapshots__/field.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/fields/__snapshots__/field.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts rename to x-pack/plugins/fleet/server/services/epm/fields/field.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/fleet/server/services/epm/fields/field.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts rename to x-pack/plugins/fleet/server/services/epm/fields/field.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/base.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/base.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/base.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/coredns.logs.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/coredns.logs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/coredns.logs.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/coredns.logs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/tests/system.yml b/x-pack/plugins/fleet/server/services/epm/fields/tests/system.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/fields/tests/system.yml rename to x-pack/plugins/fleet/server/services/epm/fields/tests/system.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.test.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/coredns.logs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.access.ecs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.error.ecs.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/nginx.fields.yml diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/test_data.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/tests/test_data.ts rename to x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/tests/test_data.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts rename to x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/assets.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/fleet/server/services/epm/packages/assets.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts rename to x-pack/plugins/fleet/server/services/epm/packages/assets.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/bulk_install_packages.ts rename to x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/ensure_installed_default_packages.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts rename to x-pack/plugins/fleet/server/services/epm/packages/get.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/get_install_type.test.ts rename to x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts rename to x-pack/plugins/fleet/server/services/epm/packages/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts rename to x-pack/plugins/fleet/server/services/epm/packages/install.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts rename to x-pack/plugins/fleet/server/services/epm/packages/remove.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts b/x-pack/plugins/fleet/server/services/epm/registry/extract.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/extract.ts rename to x-pack/plugins/fleet/server/services/epm/registry/extract.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/index.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts rename to x-pack/plugins/fleet/server/services/epm/registry/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/proxy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts rename to x-pack/plugins/fleet/server/services/epm/registry/proxy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts rename to x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/requests.test.ts rename to x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts rename to x-pack/plugins/fleet/server/services/epm/registry/requests.ts diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts b/x-pack/plugins/fleet/server/services/epm/registry/streams.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/streams.ts rename to x-pack/plugins/fleet/server/services/epm/registry/streams.ts diff --git a/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts b/x-pack/plugins/fleet/server/services/es_index_pattern.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts rename to x-pack/plugins/fleet/server/services/es_index_pattern.ts diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/index.ts rename to x-pack/plugins/fleet/server/services/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/index.ts b/x-pack/plugins/fleet/server/services/install_script/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/index.ts rename to x-pack/plugins/fleet/server/services/install_script/index.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/linux.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/linux.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/linux.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/macos.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/macos.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/macos.ts diff --git a/x-pack/plugins/ingest_manager/server/services/install_script/install_templates/types.ts b/x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/install_script/install_templates/types.ts rename to x-pack/plugins/fleet/server/services/install_script/install_templates/types.ts diff --git a/x-pack/plugins/ingest_manager/server/services/license.ts b/x-pack/plugins/fleet/server/services/license.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/license.ts rename to x-pack/plugins/fleet/server/services/license.ts diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/output.ts rename to x-pack/plugins/fleet/server/services/output.ts diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/package_policy.test.ts rename to x-pack/plugins/fleet/server/services/package_policy.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/package_policy.ts rename to x-pack/plugins/fleet/server/services/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.test.ts b/x-pack/plugins/fleet/server/services/saved_object.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/saved_object.test.ts rename to x-pack/plugins/fleet/server/services/saved_object.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.ts b/x-pack/plugins/fleet/server/services/saved_object.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/saved_object.ts rename to x-pack/plugins/fleet/server/services/saved_object.ts diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/settings.ts rename to x-pack/plugins/fleet/server/services/settings.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup.test.ts rename to x-pack/plugins/fleet/server/services/setup.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup.ts rename to x-pack/plugins/fleet/server/services/setup.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts b/x-pack/plugins/fleet/server/services/setup_utils.test.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup_utils.test.ts rename to x-pack/plugins/fleet/server/services/setup_utils.test.ts diff --git a/x-pack/plugins/ingest_manager/server/services/setup_utils.ts b/x-pack/plugins/fleet/server/services/setup_utils.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/services/setup_utils.ts rename to x-pack/plugins/fleet/server/services/setup_utils.ts diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/index.tsx rename to x-pack/plugins/fleet/server/types/index.tsx diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/fleet/server/types/models/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/agent.ts rename to x-pack/plugins/fleet/server/types/models/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/agent_policy.ts rename to x-pack/plugins/fleet/server/types/models/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/server/types/models/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/types/models/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/index.ts b/x-pack/plugins/fleet/server/types/models/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/index.ts rename to x-pack/plugins/fleet/server/types/models/index.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/output.ts rename to x-pack/plugins/fleet/server/types/models/output.ts diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/models/package_policy.ts rename to x-pack/plugins/fleet/server/types/models/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts rename to x-pack/plugins/fleet/server/types/rest_spec/agent.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/agent_policy.ts rename to x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts b/x-pack/plugins/fleet/server/types/rest_spec/common.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts rename to x-pack/plugins/fleet/server/types/rest_spec/common.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/fleet/server/types/rest_spec/enrollment_api_key.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/enrollment_api_key.ts rename to x-pack/plugins/fleet/server/types/rest_spec/enrollment_api_key.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts rename to x-pack/plugins/fleet/server/types/rest_spec/epm.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts rename to x-pack/plugins/fleet/server/types/rest_spec/index.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/install_script.ts b/x-pack/plugins/fleet/server/types/rest_spec/install_script.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/install_script.ts rename to x-pack/plugins/fleet/server/types/rest_spec/install_script.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts b/x-pack/plugins/fleet/server/types/rest_spec/output.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts rename to x-pack/plugins/fleet/server/types/rest_spec/output.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/package_policy.ts rename to x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts rename to x-pack/plugins/fleet/server/types/rest_spec/settings.ts diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/setup.ts b/x-pack/plugins/fleet/server/types/rest_spec/setup.ts similarity index 100% rename from x-pack/plugins/ingest_manager/server/types/rest_spec/setup.ts rename to x-pack/plugins/fleet/server/types/rest_spec/setup.ts diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index c654288aaceb8..5094aa2763a01 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -10,7 +10,7 @@ import { ManagementAppMountParams } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { CoreSetup, CoreStart } from '../../../../../src/core/public'; -import { IngestManagerSetup } from '../../../ingest_manager/public'; +import { IngestManagerSetup } from '../../../fleet/public'; import { IndexMgmtMetricsType } from '../types'; import { UiMetricService, NotificationService, HttpService } from './services'; import { ExtensionsService } from '../services'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index 52528d3c51145..c15af4f19827b 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -9,7 +9,7 @@ import { CoreSetup } from 'src/core/public'; import { ManagementAppMountParams } from 'src/plugins/management/public/'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { IngestManagerSetup } from '../../../ingest_manager/public'; +import { IngestManagerSetup } from '../../../fleet/public'; import { PLUGIN } from '../../common/constants'; import { ExtensionsService } from '../services'; import { IndexMgmtMetricsType, StartDependencies } from '../types'; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index f860b89b0ba0c..34d060d935415 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -5,7 +5,7 @@ */ import { ExtensionsSetup } from './services'; -import { IngestManagerSetup } from '../../ingest_manager/public'; +import { IngestManagerSetup } from '../../fleet/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginStart } from '../../../../src/plugins/share/public'; diff --git a/x-pack/plugins/security_solution/README.md b/x-pack/plugins/security_solution/README.md index 6680dbf1a149b..d9aa4a6cfebbe 100644 --- a/x-pack/plugins/security_solution/README.md +++ b/x-pack/plugins/security_solution/README.md @@ -97,7 +97,7 @@ PACKAGE_REGISTRY_URL_OVERRIDE= { diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts index 8c2dabae21bbd..551e0ecfdcb4f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts @@ -6,7 +6,7 @@ import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { cloneDeep } from 'lodash'; -import { PackagePolicy } from '../../../../../ingest_manager/common'; +import { PackagePolicy } from '../../../../../fleet/common'; export const migratePackagePolicyToV7110: SavedObjectMigrationFn = ( packagePolicyDoc diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 79157018c315a..1d64578a6a7f1 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -5,7 +5,7 @@ */ import { ApplicationStart } from 'kibana/public'; -import { NewPackagePolicy, PackagePolicy } from '../../../../ingest_manager/common'; +import { NewPackagePolicy, PackagePolicy } from '../../../../fleet/common'; import { ManifestSchema } from '../schema/manifest'; export * from './trusted_apps'; diff --git a/x-pack/plugins/security_solution/public/app/home/setup.tsx b/x-pack/plugins/security_solution/public/app/home/setup.tsx index 3f4b0c19e7035..c3567e34a0411 100644 --- a/x-pack/plugins/security_solution/public/app/home/setup.tsx +++ b/x-pack/plugins/security_solution/public/app/home/setup.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { IngestManagerStart } from '../../../../ingest_manager/public'; +import { IngestManagerStart } from '../../../../fleet/public'; export const Setup: React.FunctionComponent<{ ingestManager: IngestManagerStart; diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts index 48f826d1c3a91..01b14bd411282 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/upgrade.ts @@ -11,7 +11,7 @@ import { appRoutesService, CheckPermissionsResponse, BulkInstallPackagesResponse, -} from '../../../../../ingest_manager/common'; +} from '../../../../../fleet/common'; import { StartServices } from '../../../types'; import { useIngestEnabledCheck } from './ingest_enabled'; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index 5f8ab723df107..ff3fe7517e64a 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IngestManagerStart, - registerPackagePolicyComponent, -} from '../../../../../ingest_manager/public'; +import { IngestManagerStart, registerPackagePolicyComponent } from '../../../../../fleet/public'; import { dataPluginMock, Start as DataPublicStartMock, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index dce135dd213b3..37a61b4adf020 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -11,7 +11,7 @@ import { } from '../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../common/types'; import { GetPolicyListResponse } from '../../policy/types'; -import { GetPackagesResponse } from '../../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../../fleet/common'; import { EndpointState } from '../types'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 80b2d2b0192f8..a78bd5fde3216 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -26,7 +26,7 @@ import { sendGetAgentPolicyList, sendGetFleetAgentsWithEndpoint, } from '../../policy/store/policy_list/services/ingest'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../ingest_manager/common'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common'; import { metadataCurrentIndexPattern } from '../../../../../common/endpoint/constants'; import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index cd8ebb4d0ec50..5b14b7d658965 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -25,7 +25,7 @@ import { GetAgentPoliciesResponseItem, GetPackagesResponse, GetAgentsResponse, -} from '../../../../../../ingest_manager/common/types/rest_spec'; +} from '../../../../../../fleet/common/types/rest_spec'; import { GetPolicyListResponse } from '../../policy/types'; const generator = new EndpointDocGenerator('seed'); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index e3e2dc7b55a5e..ec22c522c3d0a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -14,7 +14,7 @@ import { MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; -import { GetPackagesResponse } from '../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../fleet/common'; import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; export interface EndpointState { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx index c0763a21f0947..dd7475361b950 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx @@ -29,7 +29,7 @@ import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app import { getEndpointDetailsPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; -import { AgentDetailsReassignPolicyAction } from '../../../../../../../ingest_manager/public'; +import { AgentDetailsReassignPolicyAction } from '../../../../../../../fleet/public'; import { EndpointPolicyLink } from '../components/endpoint_policy_link'; const HostIds = styled(EuiListGroupItem)` diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index c5d3c3c25313d..670e8dc965ad9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -51,7 +51,7 @@ import { CreatePackagePolicyRouteState, AgentPolicyDetailsDeployAgentAction, pagePathGetters, -} from '../../../../../../ingest_manager/public'; +} from '../../../../../../fleet/public'; import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts index f729dfbd9a29a..f5a219bce4a6b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GetAgentStatusResponse } from '../../../../../../../ingest_manager/common/types/rest_spec'; +import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec'; import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; import { PolicyDetailsState } from '../../types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts index 562cd6807f68a..cfd053948922b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts @@ -6,10 +6,7 @@ import { PolicyData } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; -import { - GetAgentStatusResponse, - GetPackagesResponse, -} from '../../../../../../../ingest_manager/common'; +import { GetAgentStatusResponse, GetPackagesResponse } from '../../../../../../../fleet/common'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts index 03e65bd6f43cb..524c44406bd33 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts @@ -8,7 +8,7 @@ import { PolicyListState } from '../../types'; import { Store, applyMiddleware, createStore } from 'redux'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../ingest_manager/common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../fleet/common'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts index fddd242e092ad..e57eb0e32e516 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts @@ -18,7 +18,7 @@ import { DeletePackagePoliciesResponse, DeletePackagePoliciesRequest, GetAgentStatusResponse, -} from '../../../../../../../ingest_manager/common'; +} from '../../../../../../../fleet/common'; export const policyListMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts index 00e7b0ca51ecf..43a12868368c5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts @@ -11,7 +11,7 @@ import { sendGetEndpointSpecificPackagePolicies, } from './ingest'; import { httpServiceMock } from '../../../../../../../../../../src/core/public/mocks'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../../ingest_manager/common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../../fleet/common'; import { policyListApiPathHandlers } from '../test_mock_utils'; describe('ingest service', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts index 204dfe437815e..a241f8d1c4556 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts @@ -15,7 +15,7 @@ import { GetPackagesResponse, GetAgentPoliciesRequest, GetAgentPoliciesResponse, -} from '../../../../../../../../ingest_manager/common'; +} from '../../../../../../../../fleet/common'; import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; import { NewPolicyData } from '../../../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts index 1093aed0608d5..3c8b1f913c868 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts @@ -8,7 +8,7 @@ import { HttpStart } from 'kibana/public'; import { INGEST_API_EPM_PACKAGES, INGEST_API_PACKAGE_POLICIES } from './services/ingest'; import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; import { GetPolicyListResponse } from '../../types'; -import { GetPackagesResponse } from '../../../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../../../fleet/common'; const generator = new EndpointDocGenerator('policy-list'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index c7d426da05508..152caff3714b0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -18,7 +18,7 @@ import { GetPackagePoliciesResponse, GetPackagesResponse, UpdatePackagePolicyResponse, -} from '../../../../../ingest_manager/common'; +} from '../../../../../fleet/common'; /** * Policy list store state diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx index 05e77c57ae52b..0be5f119e5eff 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx @@ -23,7 +23,7 @@ import { CustomConfigurePackagePolicyContent, CustomConfigurePackagePolicyProps, pagePathGetters, -} from '../../../../../../../ingest_manager/public'; +} from '../../../../../../../fleet/public'; import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing'; import { MANAGEMENT_APP_ID } from '../../../../common/constants'; import { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 78226a858bfeb..274032eea0c5d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -46,7 +46,7 @@ import { SecurityPageName } from '../../../../app/types'; import { useFormatUrl } from '../../../../common/components/link_to'; import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { CreatePackagePolicyRouteState } from '../../../../../../ingest_manager/public'; +import { CreatePackagePolicyRouteState } from '../../../../../../fleet/public'; import { MANAGEMENT_APP_ID } from '../../../common/constants'; import { AdministrationListPage } from '../../../components/administration_list_page'; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index d72a8b9261587..5e8400c574235 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -15,7 +15,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { TelemetryManagementSectionPluginSetup } from '../../../../src/plugins/telemetry_management_section/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import { IngestManagerStart } from '../../ingest_manager/public'; +import { IngestManagerStart } from '../../fleet/public'; import { PluginStart as ListsPluginStart } from '../../lists/public'; import { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts index 3796c3f62d0ee..9ad094086b632 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts @@ -11,11 +11,11 @@ import { KbnClient, ToolingLog } from '@kbn/dev-utils'; import { AxiosResponse } from 'axios'; import { indexHostsAndAlerts } from '../../common/endpoint/index_data'; import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/generate_data'; -import { AGENTS_SETUP_API_ROUTES, SETUP_API_ROUTE } from '../../../ingest_manager/common/constants'; +import { AGENTS_SETUP_API_ROUTES, SETUP_API_ROUTE } from '../../../fleet/common/constants'; import { CreateFleetSetupResponse, PostIngestSetupResponse, -} from '../../../ingest_manager/common/types/rest_spec'; +} from '../../../fleet/common/types/rest_spec'; import { KbnClientWithApiKeySupport } from './kbn_client_with_api_key_support'; main(); diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 2cc8245e521bf..c7f49f479583e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -10,11 +10,7 @@ import { SavedObjectsClientContract, } from 'src/core/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { - AgentService, - IngestManagerStartContract, - PackageService, -} from '../../../ingest_manager/server'; +import { AgentService, IngestManagerStartContract, PackageService } from '../../../fleet/server'; import { PluginStartContract as AlertsPluginStartContract } from '../../../alerts/server'; import { getPackagePolicyCreateCallback } from './ingest_integration'; import { ManifestManager } from './services/artifacts'; @@ -24,7 +20,7 @@ import { metadataQueryStrategyV1, metadataQueryStrategyV2, } from './routes/metadata/support/query_strategies'; -import { ElasticsearchAssetType } from '../../../ingest_manager/common/types/models'; +import { ElasticsearchAssetType } from '../../../fleet/common/types/models'; import { metadataTransformPrefix } from '../../common/endpoint/constants'; import { AppClientFactory } from '../client'; import { ConfigType } from '../config'; diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts index 1db3e9984284d..321d67a441f6d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts @@ -5,7 +5,7 @@ */ import { httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; -import { createNewPackagePolicyMock } from '../../../ingest_manager/common/mocks'; +import { createNewPackagePolicyMock } from '../../../fleet/common/mocks'; import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; import { getManifestManagerMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts index 279603cd621c8..3c94bdc63b0fb 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts @@ -6,9 +6,9 @@ import { PluginStartContract as AlertsStartContract } from '../../../alerts/server'; import { SecurityPluginSetup } from '../../../security/server'; -import { ExternalCallback } from '../../../ingest_manager/server'; +import { ExternalCallback } from '../../../fleet/server'; import { KibanaRequest, Logger, RequestHandlerContext } from '../../../../../src/core/server'; -import { NewPackagePolicy } from '../../../ingest_manager/common/types/models'; +import { NewPackagePolicy } from '../../../fleet/common/types/models'; import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; import { NewPolicyData } from '../../common/endpoint/types'; import { ManifestManager } from './services/artifacts'; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts index cdfbb551940e1..44b1d5fefe4e1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PackagePolicy } from '../../../../../ingest_manager/common'; -import { createPackagePolicyMock } from '../../../../../ingest_manager/common/mocks'; +import { PackagePolicy } from '../../../../../fleet/common'; +import { createPackagePolicyMock } from '../../../../../fleet/common/mocks'; import { InternalArtifactCompleteSchema } from '../../schemas/artifacts'; import { getInternalArtifactMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 98b971a00710d..588404fd516d0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -14,8 +14,8 @@ import { IngestManagerStartContract, ExternalCallback, PackageService, -} from '../../../ingest_manager/server'; -import { createPackagePolicyServiceMock } from '../../../ingest_manager/server/mocks'; +} from '../../../fleet/server'; +import { createPackagePolicyServiceMock } from '../../../fleet/server/mocks'; import { AppClientFactory } from '../client'; import { createMockConfig } from '../lib/detection_engine/routes/__mocks__'; import { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts index d825841f1e257..401f7e97470be 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_exception_list.ts @@ -12,7 +12,7 @@ import { } from 'src/core/server'; import LRU from 'lru-cache'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { authenticateAgentWithAccessToken } from '../../../../../ingest_manager/server/services/agents/authenticate'; +import { authenticateAgentWithAccessToken } from '../../../../../fleet/server/services/agents/authenticate'; import { LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG } from '../../../../common/endpoint/constants'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; import { ArtifactConstants } from '../../lib/artifacts'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 194f0a1c2e7c5..f2011e99565c8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -15,7 +15,7 @@ import { MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; -import { Agent, AgentStatus } from '../../../../../ingest_manager/common/types/models'; +import { Agent, AgentStatus } from '../../../../../fleet/common/types/models'; import { EndpointAppContext, HostListQueryResult } from '../../types'; import { GetMetadataListRequestSchema, GetMetadataRequestSchema } from './index'; import { findAllUnenrolledAgentIds } from './support/unenroll'; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 227a3e6fd9657..46a4363936b3d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -39,9 +39,9 @@ import { Agent, ElasticsearchAssetType, EsAssetReference, -} from '../../../../../ingest_manager/common/types/models'; +} from '../../../../../fleet/common/types/models'; import { createV1SearchResponse, createV2SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../ingest_manager/server/services'; +import { PackageService } from '../../../../../fleet/server/services'; import { metadataTransformPrefix } from '../../../../common/endpoint/constants'; describe('test endpoint route', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts index 568917c804733..26f216f0474c2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts @@ -35,9 +35,9 @@ import { import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { Agent, EsAssetReference } from '../../../../../ingest_manager/common/types/models'; +import { Agent, EsAssetReference } from '../../../../../fleet/common/types/models'; import { createV1SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../ingest_manager/server/services'; +import { PackageService } from '../../../../../fleet/server/services'; describe('test endpoint route v1', () => { let routerMock: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts index a4b6b0750ec10..ed3c48ed6c677 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts @@ -7,10 +7,10 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { findAgentIDsByStatus } from './agent_status'; import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; -import { AgentService } from '../../../../../../ingest_manager/server/services'; +import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../mocks'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; -import { AgentStatusKueryHelper } from '../../../../../../ingest_manager/common/services'; +import { Agent } from '../../../../../../fleet/common/types/models'; +import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'; describe('test filtering endpoint hosts by agent status', () => { let mockSavedObjectClient: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts index 86f6d1a9a65e2..395b05c0887e9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.ts @@ -5,9 +5,9 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentService } from '../../../../../../ingest_manager/server'; -import { AgentStatusKueryHelper } from '../../../../../../ingest_manager/common/services'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { AgentService } from '../../../../../../fleet/server'; +import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'; +import { Agent } from '../../../../../../fleet/common/types/models'; import { HostStatus } from '../../../../../common/endpoint/types'; const STATUS_QUERY_MAP = new Map([ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts index 30c8f14287cae..cd273f785033c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts @@ -7,9 +7,9 @@ import { SavedObjectsClientContract } from 'kibana/server'; import { findAllUnenrolledAgentIds } from './unenroll'; import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks'; -import { AgentService } from '../../../../../../ingest_manager/server/services'; +import { AgentService } from '../../../../../../fleet/server/services'; import { createMockAgentService } from '../../../mocks'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { Agent } from '../../../../../../fleet/common/types/models'; describe('test find all unenrolled Agent id', () => { let mockSavedObjectClient: jest.Mocked; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts index ca2ced177ab33..1abea86c1a495 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.ts @@ -5,8 +5,8 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { AgentService } from '../../../../../../ingest_manager/server'; -import { Agent } from '../../../../../../ingest_manager/common/types/models'; +import { AgentService } from '../../../../../../fleet/server'; +import { Agent } from '../../../../../../fleet/common/types/models'; export async function findAllUnenrolledAgentIds( agentService: AgentService, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index 27dd38047e7c3..353bfb6b0f16e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -6,8 +6,8 @@ import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks'; import { Logger } from 'src/core/server'; -import { PackagePolicyServiceInterface } from '../../../../../../ingest_manager/server'; -import { createPackagePolicyServiceMock } from '../../../../../../ingest_manager/server/mocks'; +import { PackagePolicyServiceInterface } from '../../../../../../fleet/server'; +import { createPackagePolicyServiceMock } from '../../../../../../fleet/server/mocks'; import { ExceptionListClient } from '../../../../../../lists/server'; import { listMock } from '../../../../../../lists/server/mocks'; import LRU from 'lru-cache'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 40b408166b17f..f5a27b7602e8b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -6,7 +6,7 @@ import { inflateSync } from 'zlib'; import { savedObjectsClientMock } from 'src/core/server/mocks'; -import { createPackagePolicyServiceMock } from '../../../../../../ingest_manager/server/mocks'; +import { createPackagePolicyServiceMock } from '../../../../../../fleet/server/mocks'; import { ArtifactConstants, ManifestConstants, isCompleteArtifact } from '../../../lib/artifacts'; import { getManifestManagerMock, ManifestManagerMockType } from './manifest_manager.mock'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index f9836c7ecdc30..9f45f39a392f6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -6,7 +6,7 @@ import semver from 'semver'; import { Logger, SavedObjectsClientContract } from 'src/core/server'; import LRU from 'lru-cache'; -import { PackagePolicyServiceInterface } from '../../../../../../ingest_manager/server'; +import { PackagePolicyServiceInterface } from '../../../../../../fleet/server'; import { ExceptionListClient } from '../../../../../../lists/server'; import { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/manifest'; diff --git a/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts index 6abff93d6cd5c..53e3da62f4e35 100644 --- a/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/hosts/elasticsearch_adapter.test.ts @@ -34,8 +34,8 @@ import { createMockEndpointAppContextServiceStartContract, createMockPackageService, } from '../../endpoint/mocks'; -import { PackageService } from '../../../../ingest_manager/server/services'; -import { ElasticsearchAssetType } from '../../../../ingest_manager/common/types/models'; +import { PackageService } from '../../../../fleet/server/services'; +import { ElasticsearchAssetType } from '../../../../fleet/common/types/models'; jest.mock('./query.hosts.dsl', () => { return { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 43f87a0c69ab3..d8faa2436bf55 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -34,7 +34,7 @@ import { ListPluginSetup } from '../../lists/server'; import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server'; import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { IngestManagerStartContract, ExternalCallback } from '../../ingest_manager/server'; +import { IngestManagerStartContract, ExternalCallback } from '../../fleet/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts index d5e1d21b7772b..b8858100171f9 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsFindResponse } from 'src/core/server'; -import { AgentEventSOAttributes } from './../../../../ingest_manager/common/types/models/agent'; +import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, -} from '../../../../ingest_manager/common/constants/agent'; -import { Agent } from '../../../../ingest_manager/common'; +} from '../../../../fleet/common/constants/agent'; +import { Agent } from '../../../../fleet/common'; import { FLEET_ENDPOINT_PACKAGE_CONSTANT } from './fleet_saved_objects'; const testAgentId = 'testAgentId'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts index e2f7a3be6d80a..6d4b97564c90e 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts @@ -12,8 +12,8 @@ import { MockOSVersion, } from './endpoint.mocks'; import { ISavedObjectsRepository, SavedObjectsFindResponse } from 'src/core/server'; -import { AgentEventSOAttributes } from '../../../../ingest_manager/common/types/models/agent'; -import { Agent } from '../../../../ingest_manager/common'; +import { AgentEventSOAttributes } from '../../../../fleet/common/types/models/agent'; +import { Agent } from '../../../../fleet/common'; import * as endpointTelemetry from './index'; import * as fleetSavedObjects from './fleet_saved_objects'; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts index 405339aaf25e2..7ffb53dcf2a3f 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/fleet_saved_objects.ts @@ -5,12 +5,12 @@ */ import { ISavedObjectsRepository } from 'src/core/server'; -import { AgentEventSOAttributes } from './../../../../ingest_manager/common/types/models/agent'; +import { AgentEventSOAttributes } from './../../../../fleet/common/types/models/agent'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, -} from './../../../../ingest_manager/common/constants/agent'; -import { Agent, defaultPackages as FleetDefaultPackages } from '../../../../ingest_manager/common'; +} from './../../../../fleet/common/constants/agent'; +import { Agent, defaultPackages as FleetDefaultPackages } from '../../../../fleet/common'; export const FLEET_ENDPOINT_PACKAGE_CONSTANT = FleetDefaultPackages.Endpoint; diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts index f1ce9d3ad6ff3..f1642c52ef405 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts @@ -6,8 +6,8 @@ import { cloneDeep } from 'lodash'; import { ISavedObjectsRepository } from 'src/core/server'; import { SavedObject } from './../../../../../../src/core/types/saved_objects'; -import { Agent, NewAgentEvent } from './../../../../ingest_manager/common/types/models/agent'; -import { AgentMetadata } from '../../../../ingest_manager/common/types/models/agent'; +import { Agent, NewAgentEvent } from './../../../../fleet/common/types/models/agent'; +import { AgentMetadata } from '../../../../fleet/common/types/models/agent'; import { getFleetSavedObjectsMetadata, getLatestFleetEndpointEvent } from './fleet_saved_objects'; export interface AgentOSMetadataTelemetry { diff --git a/x-pack/test/common/services/ingest_manager.ts b/x-pack/test/common/services/ingest_manager.ts index 2fcfaa014b2e1..42c1d20437c7b 100644 --- a/x-pack/test/common/services/ingest_manager.ts +++ b/x-pack/test/common/services/ingest_manager.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { FtrProviderContext } from '../ftr_provider_context'; -import { fleetSetupRouteService } from '../../../plugins/ingest_manager/common'; +import { fleetSetupRouteService } from '../../../plugins/fleet/common'; export function IngestManagerProvider({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts index 7c07fd47a66af..3f32d445e1fe7 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts @@ -11,7 +11,7 @@ import { BulkInstallPackageInfo, BulkInstallPackagesResponse, IBulkInstallPackageHTTPError, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts index 8e8e4f010bcb5..2ae4273bfa7e8 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import { AssetReference } from '../../../../plugins/ingest_manager/common'; +import { AssetReference } from '../../../../plugins/fleet/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts index af64d83a8a139..429d70836a6c1 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts @@ -10,7 +10,7 @@ import { skipIfNoDockerRegistry } from '../../helpers'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts index 2e7ab199a7fbc..edd6cd4e107a5 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL, -} from '../../../../plugins/ingest_manager/common'; +} from '../../../../plugins/fleet/common'; import { skipIfNoDockerRegistry } from '../../helpers'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts index 15cff79475f08..3bab7411bed88 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; -import { GetInfoResponse, Installed } from '../../../../plugins/ingest_manager/common'; +import { GetInfoResponse, Installed } from '../../../../plugins/fleet/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts index c92dac3334de3..1fc3eac026b65 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/template.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { getTemplate } from '../../../../plugins/ingest_manager/server/services/epm/elasticsearch/template/template'; +import { getTemplate } from '../../../../plugins/fleet/server/services/epm/elasticsearch/template/template'; export default function ({ getService }: FtrProviderContext) { const indexPattern = 'foo'; diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts index 12d7780ad50d4..cbb07b91caf34 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts @@ -9,7 +9,7 @@ import semver from 'semver'; import { FtrProviderContext } from '../../../../api_integration/ftr_provider_context'; import { setupIngest } from './services'; import { skipIfNoDockerRegistry } from '../../../helpers'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../plugins/ingest_manager/common'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../plugins/fleet/common'; const makeSnapshotVersion = (version: string) => { return version.endsWith('-SNAPSHOT') ? version : `${version}-SNAPSHOT`; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 3e3aeee305433..3103d461669f1 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../../plugins/ingest_manager/server'; +import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../../plugins/fleet/server'; import { FtrProviderContext } from '../../ftr_provider_context'; import { isRegistryEnabled, diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts index 7fc51f5c8e39c..1b1d0bf96a187 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts @@ -15,7 +15,7 @@ import { GetPackagePoliciesResponse, GetFullAgentPolicyResponse, GetPackagesResponse, -} from '../../../plugins/ingest_manager/common'; +} from '../../../plugins/fleet/common'; import { factory as policyConfigFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config'; import { Immutable } from '../../../plugins/security_solution/common/endpoint/types'; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 3d344c1b3b51b..cd0a861d9a2f0 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -5,7 +5,7 @@ */ import { FtrProviderContext } from '../ftr_provider_context'; import { isRegistryEnabled, getRegistryUrlFromTestEnv } from '../registry'; -import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../plugins/ingest_manager/server'; +import { getRegistryUrl as getRegistryUrlFromIngest } from '../../../plugins/fleet/server'; export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) { const { loadTestFile, getService } = providerContext; From 89547bc0166e375e3249edffd94dbb2797085f52 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 9 Nov 2020 10:07:59 -0600 Subject: [PATCH 19/87] [deb/rpm] Set pid.file to /run/kibana/kibana.pid (#82209) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../service_templates/systemd/etc/systemd/system/kibana.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service index 05724db8799f3..7a1508d91b213 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service @@ -15,7 +15,7 @@ Environment=KBN_PATH_CONF=/etc/kibana EnvironmentFile=-/etc/default/kibana EnvironmentFile=-/etc/sysconfig/kibana -ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" +ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" --pid.file="/run/kibana/kibana.pid" Restart=on-failure RestartSec=3 From bc9dd3ade0b0361cbab79b76391235828588fcab Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 9 Nov 2020 11:29:31 -0500 Subject: [PATCH 20/87] Allow the repository to search across all namespaces (#82863) --- .../lib/search_dsl/query_params.test.ts | 21 +++++--- .../service/lib/search_dsl/query_params.ts | 51 +++++++++---------- .../apis/saved_objects/find.js | 25 ++++++++- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index a8c5df8d64630..c35ec809fcf8d 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -21,8 +21,8 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; +import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; -import { ALL_NAMESPACES_STRING } from '../utils'; import { getQueryParams, getClauseForReference } from './query_params'; const registerTypes = (registry: SavedObjectTypeRegistry) => { @@ -101,20 +101,25 @@ describe('#getQueryParams', () => { const createTypeClause = (type: string, namespaces?: string[]) => { if (registry.isMultiNamespace(type)) { - const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; + const array = [...(namespaces ?? [DEFAULT_NAMESPACE_STRING]), ALL_NAMESPACES_STRING]; + + const namespacesClause = { terms: { namespaces: array } }; return { bool: { - must: expect.arrayContaining([{ terms: { namespaces: array } }]), + must: namespaces?.includes(ALL_NAMESPACES_STRING) + ? expect.not.arrayContaining([namespacesClause]) + : expect.arrayContaining([namespacesClause]), must_not: [{ exists: { field: 'namespace' } }], }, }; } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const nonDefaultNamespaces = namespaces?.filter((n) => n !== DEFAULT_NAMESPACE_STRING) ?? []; + const searchingAcrossAllNamespaces = namespaces?.includes(ALL_NAMESPACES_STRING) ?? false; const should: any = []; - if (nonDefaultNamespaces.length > 0) { + if (nonDefaultNamespaces.length > 0 && !searchingAcrossAllNamespaces) { should.push({ terms: { namespace: nonDefaultNamespaces } }); } - if (namespaces?.includes('default')) { + if (namespaces?.includes(DEFAULT_NAMESPACE_STRING)) { should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); } return { @@ -352,7 +357,7 @@ describe('#getQueryParams', () => { expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces))); }; - it('normalizes and deduplicates provided namespaces', () => { + it('deduplicates provided namespaces', () => { const result = getQueryParams({ registry, search: '*', @@ -361,7 +366,7 @@ describe('#getQueryParams', () => { expectResult( result, - ...ALL_TYPES.map((x) => createTypeClause(x, ['foo', 'default', 'bar'])) + ...ALL_TYPES.map((x) => createTypeClause(x, ['foo', '*', 'bar', 'default'])) ); }); diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index f73777c4f454f..2ecba42e408e7 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -75,34 +75,41 @@ function getClauseForType( if (namespaces.length === 0) { throw new Error('cannot specify empty namespaces array'); } + const searchAcrossAllNamespaces = namespaces.includes(ALL_NAMESPACES_STRING); + if (registry.isMultiNamespace(type)) { + const namespacesFilterClause = searchAcrossAllNamespaces + ? {} + : { terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] } }; + return { bool: { - must: [ - { term: { type } }, - { terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] } }, - ], + must: [{ term: { type } }, namespacesFilterClause], must_not: [{ exists: { field: 'namespace' } }], }, }; } else if (registry.isSingleNamespace(type)) { const should: Array> = []; const eligibleNamespaces = namespaces.filter((x) => x !== DEFAULT_NAMESPACE_STRING); - if (eligibleNamespaces.length > 0) { + if (eligibleNamespaces.length > 0 && !searchAcrossAllNamespaces) { should.push({ terms: { namespace: eligibleNamespaces } }); } if (namespaces.includes(DEFAULT_NAMESPACE_STRING)) { should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); } - if (should.length === 0) { - // This is indicitive of a bug, and not user error. - throw new Error('unhandled search condition: expected at least 1 `should` clause.'); - } + + const shouldClauseProps = + should.length > 0 + ? { + should, + minimum_should_match: 1, + } + : {}; + return { bool: { must: [{ term: { type } }], - should, - minimum_should_match: 1, + ...shouldClauseProps, must_not: [{ exists: { field: 'namespaces' } }], }, }; @@ -181,21 +188,9 @@ export function getClauseForReference(reference: HasReferenceQueryParams) { }; } -// A de-duplicated set of namespaces makes for a more efficient query. -// -// Additionally, we treat the `*` namespace as the `default` namespace. -// In the Default Distribution, the `*` is automatically expanded to include all available namespaces. -// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` -// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, -// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place -// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. -// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 -const normalizeNamespaces = (namespacesToNormalize?: string[]) => - namespacesToNormalize - ? Array.from( - new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) - ) - : undefined; +// A de-duplicated set of namespaces makes for a more effecient query. +const uniqNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize ? Array.from(new Set(namespacesToNormalize)) : undefined; /** * Get the "query" related keys for the search body @@ -229,10 +224,10 @@ export function getQueryParams({ { bool: { should: types.map((shouldType) => { - const normalizedNamespaces = normalizeNamespaces( + const deduplicatedNamespaces = uniqNamespaces( typeToNamespacesMap ? typeToNamespacesMap.get(shouldType) : namespaces ); - return getClauseForType(registry, normalizedNamespaces, shouldType); + return getClauseForType(registry, deduplicatedNamespaces, shouldType); }), minimum_should_match: 1, }, diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index e5da46644672b..8e8730b1e574a 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -160,7 +160,7 @@ export default function ({ getService }) { }); describe('wildcard namespace', () => { - it('should return 200 with individual responses from the default namespace', async () => + it('should return 200 with individual responses from the all namespaces', async () => await supertest .get('/api/saved_objects/_find?type=visualization&fields=title&namespaces=*') .expect(200) @@ -168,7 +168,7 @@ export default function ({ getService }) { expect(resp.body).to.eql({ page: 1, per_page: 20, - total: 1, + total: 2, saved_objects: [ { type: 'visualization', @@ -189,6 +189,27 @@ export default function ({ getService }) { ], updated_at: '2017-09-21T18:51:23.794Z', }, + { + attributes: { + title: 'Count of requests', + }, + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + migrationVersion: { + visualization: '7.10.0', + }, + namespaces: ['foo-ns'], + references: [ + { + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], + score: 0, + type: 'visualization', + updated_at: '2017-09-21T18:51:23.794Z', + version: 'WzYsMV0=', + }, ], }); expect(resp.body.saved_objects[0].migrationVersion).to.be.ok(); From 55cf3bd0a646b504db76b2050413b0101671b15c Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 9 Nov 2020 11:33:57 -0500 Subject: [PATCH 21/87] Update grunt and related packages (#79327) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 3 +- yarn.lock | 151 ++++++++++++++++++++++++++------------------------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 3500ed78ad7d1..353d3dc85356c 100644 --- a/package.json +++ b/package.json @@ -663,10 +663,9 @@ "graphql-codegen-typescript-common": "^0.18.2", "graphql-codegen-typescript-resolvers": "^0.18.2", "graphql-codegen-typescript-server": "^0.18.2", - "grunt": "1.0.4", + "grunt": "1.3.0", "grunt-available-tasks": "^0.6.3", "grunt-babel": "^8.0.0", - "grunt-cli": "^1.2.0", "grunt-contrib-clean": "^1.1.0", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-watch": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index edbc186c22243..b59c134968c18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9094,7 +9094,7 @@ chai@^4.1.2: pathval "^1.1.0" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2, chalk@~2.4.1: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9122,7 +9122,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -9690,11 +9690,6 @@ coffeescript@1.12.7: resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== -coffeescript@~1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.10.0.tgz#e7aa8301917ef621b35d8a39f348dcdd1db7e33e" - integrity sha1-56qDAZF+9iGzXYo580jc3R234z4= - collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -11103,7 +11098,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -dateformat@^1.0.11, dateformat@~1.0.12: +dateformat@^1.0.11: version "1.0.12" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= @@ -11111,7 +11106,7 @@ dateformat@^1.0.11, dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -dateformat@^3.0.2: +dateformat@^3.0.2, dateformat@~3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== @@ -13151,7 +13146,7 @@ exit-hook@^2.2.0: resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.0.tgz#f5502f92179018e867f2d8ee4428392da7f3894e" integrity sha512-YFH+2oGdldRH5GqGpnaiKbBxWHMmuXHmKTMtUC58kWSOrnTf95rKITVSFTTtas14DWvWpih429+ffAvFetPwNA== -exit@^0.1.2, exit@~0.1.1: +exit@^0.1.2, exit@~0.1.1, exit@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= @@ -14586,7 +14581,7 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -14609,18 +14604,6 @@ glob@~5.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~7.0.0: - version "7.0.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" - integrity sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -15165,15 +15148,16 @@ grunt-babel@^8.0.0: resolved "https://registry.yarnpkg.com/grunt-babel/-/grunt-babel-8.0.0.tgz#92ef63aafadf938c488dc2f926ac9846e0c93d1b" integrity sha512-WuiZFvGzcyzlEoPIcY1snI234ydDWeWWV5bpnB7PZsOLHcDsxWKnrR1rMWEUsbdVPPjvIirwFNsuo4CbJmsdFQ== -grunt-cli@^1.2.0, grunt-cli@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8" - integrity sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg= +grunt-cli@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.3.2.tgz#60f12d12c1b5aae94ae3469c6b5fe24e960014e8" + integrity sha512-8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ== dependencies: - findup-sync "~0.3.0" grunt-known-options "~1.1.0" - nopt "~3.0.6" - resolve "~1.1.0" + interpret "~1.1.0" + liftoff "~2.5.0" + nopt "~4.0.1" + v8flags "~3.1.1" grunt-contrib-clean@^1.1.0: version "1.1.0" @@ -15206,35 +15190,35 @@ grunt-known-options@~1.1.0: resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149" integrity sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk= -grunt-legacy-log-utils@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz#d2f442c7c0150065d9004b08fd7410d37519194e" - integrity sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA== +grunt-legacy-log-utils@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" + integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== dependencies: - chalk "~2.4.1" - lodash "~4.17.10" + chalk "~4.1.0" + lodash "~4.17.19" -grunt-legacy-log@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz#c8cd2c6c81a4465b9bbf2d874d963fef7a59ffb9" - integrity sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw== +grunt-legacy-log@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" + integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== dependencies: colors "~1.1.2" - grunt-legacy-log-utils "~2.0.0" + grunt-legacy-log-utils "~2.1.0" hooker "~0.2.3" - lodash "~4.17.5" + lodash "~4.17.19" -grunt-legacy-util@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz#e10624e7c86034e5b870c8a8616743f0a0845e42" - integrity sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A== +grunt-legacy-util@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.0.tgz#34d20f2a26c6adebfe9a9bdc8823f7016b0369c3" + integrity sha512-ZEmYFB44bblwPE2oz3q3ygfF6hseQja9tx8I3UZIwbUik32FMWewA+d1qSFicMFB+8dNXDkh35HcDCWlpRsGlA== dependencies: async "~1.5.2" exit "~0.1.1" getobject "~0.1.0" hooker "~0.2.3" - lodash "~4.17.10" - underscore.string "~3.3.4" + lodash "~4.17.20" + underscore.string "~3.3.5" which "~1.3.0" grunt-peg@^2.0.1: @@ -15251,28 +15235,26 @@ grunt-run@0.8.1: dependencies: strip-ansi "^3.0.0" -grunt@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.0.4.tgz#c799883945a53a3d07622e0737c8f70bfe19eb38" - integrity sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ== +grunt@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.3.0.tgz#55db6ccd80c6fb53722e496f680620a2e681f809" + integrity sha512-6ILlMXv11/4cxuhSMfSU+SfvbxrPuqZrAtLN64+tZpQ3DAKfSQPQHRbTjSbdtxfyQhGZPtN0bDZJ/LdCM5WXXA== dependencies: - coffeescript "~1.10.0" - dateformat "~1.0.12" + dateformat "~3.0.3" eventemitter2 "~0.4.13" - exit "~0.1.1" + exit "~0.1.2" findup-sync "~0.3.0" - glob "~7.0.0" - grunt-cli "~1.2.0" + glob "~7.1.6" + grunt-cli "~1.3.2" grunt-known-options "~1.1.0" - grunt-legacy-log "~2.0.0" - grunt-legacy-util "~1.1.1" + grunt-legacy-log "~3.0.0" + grunt-legacy-util "~2.0.0" iconv-lite "~0.4.13" - js-yaml "~3.13.0" - minimatch "~3.0.2" - mkdirp "~0.5.1" + js-yaml "~3.14.0" + minimatch "~3.0.4" + mkdirp "~1.0.4" nopt "~3.0.6" - path-is-absolute "~1.0.0" - rimraf "~2.6.2" + rimraf "~3.0.2" gud@^1.0.0: version "1.0.0" @@ -16431,6 +16413,11 @@ interpret@^2.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= + intl-format-cache@^2.0.5, intl-format-cache@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.1.0.tgz#04a369fecbfad6da6005bae1f14333332dcf9316" @@ -18070,7 +18057,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1: +js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -18078,7 +18065,7 @@ js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1 argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.14.0: +js-yaml@^3.14.0, js-yaml@~3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -18715,6 +18702,20 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" +liftoff@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" + integrity sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew= + dependencies: + extend "^3.0.0" + findup-sync "^2.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + linebreak@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.0.2.tgz#4b5781733e9a9eb2849dba2f963e47c887f8aa06" @@ -19189,7 +19190,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.19, lodash@~4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19937,7 +19938,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -20072,7 +20073,7 @@ mkdirp@^0.3.5: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= -mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -21879,7 +21880,7 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1, path-is-absolute@~1.0.0: +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -24610,7 +24611,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@~1.1.0, resolve@~1.1.7: +resolve@1.1.7, resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -24715,7 +24716,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -24736,7 +24737,7 @@ rimraf@^2.7.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -27696,7 +27697,7 @@ undefsafe@^2.0.2: dependencies: debug "^2.2.0" -underscore.string@~3.3.4: +underscore.string@~3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg== @@ -28384,7 +28385,7 @@ v8-to-istanbul@^6.0.1: convert-source-map "^1.6.0" source-map "^0.7.3" -v8flags@^3.0.1: +v8flags@^3.0.1, v8flags@~3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== From 45ddd69ca220485ed481ed691e59ff1ea7edbeef Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 9 Nov 2020 17:34:20 +0100 Subject: [PATCH 22/87] Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR/API requests. (#82817) * Prevent Kerberos and PKI providers from initiating a new session for unauthenticated XHR requests. * Review#1: fix comment. --- .github/CODEOWNERS | 3 - .../authentication/providers/kerberos.test.ts | 63 +++++--- .../authentication/providers/kerberos.ts | 37 ++--- .../authentication/providers/pki.test.ts | 138 ++++++++++++------ .../server/authentication/providers/pki.ts | 20 +-- x-pack/scripts/functional_tests.js | 10 +- .../kerberos_api_integration/apis/index.ts | 14 -- .../ftr_provider_context.d.ts | 11 -- .../test/kerberos_api_integration/services.ts | 15 -- .../ftr_provider_context.d.ts | 11 -- x-pack/test/oidc_api_integration/services.ts | 14 -- x-pack/test/pki_api_integration/apis/index.ts | 14 -- .../ftr_provider_context.d.ts | 11 -- x-pack/test/pki_api_integration/services.ts | 14 -- .../fixtures/kerberos}/README.md | 0 .../fixtures/kerberos}/kerberos_tools.ts | 0 .../fixtures/kerberos}/krb5.conf | 0 .../fixtures/kerberos}/krb5.keytab | Bin .../fixtures/oidc}/README.md | 0 .../fixtures/oidc}/jwks.json | 0 .../fixtures/oidc}/jwks_private.pem | 0 .../fixtures/oidc}/jwks_public.pem | 0 .../fixtures/oidc}/oidc_provider/kibana.json | 0 .../oidc}/oidc_provider/server/index.ts | 2 +- .../oidc}/oidc_provider/server/init_routes.ts | 2 +- .../fixtures/oidc}/oidc_tools.ts | 0 .../fixtures/pki}/README.md | 0 .../fixtures/pki}/first_client.p12 | Bin .../fixtures/pki}/kibana_ca.crt | 0 .../fixtures/pki}/kibana_ca.key | 0 .../fixtures/pki}/second_client.p12 | Bin .../fixtures/pki}/untrusted_client.p12 | Bin .../kerberos.config.ts} | 14 +- .../kerberos_anonymous_access.config.ts} | 4 +- .../login_selector.config.ts | 10 +- .../oidc.config.ts} | 8 +- .../oidc_implicit_flow.config.ts} | 6 +- .../pki.config.ts} | 6 +- .../test/security_api_integration/services.ts | 1 + .../tests/kerberos}/index.ts | 4 +- .../tests/kerberos}/kerberos_login.ts | 28 ++-- .../login_selector/basic_functionality.ts | 8 +- .../oidc/authorization_code_flow}/index.ts | 4 +- .../authorization_code_flow/oidc_auth.ts | 4 +- .../tests/oidc/implicit_flow}/index.ts | 4 +- .../tests/oidc}/implicit_flow/oidc_auth.ts | 4 +- .../tests/pki}/index.ts | 4 +- .../tests/pki}/pki_auth.ts | 66 +++++---- .../test/security_functional/oidc.config.ts | 7 +- 49 files changed, 271 insertions(+), 290 deletions(-) delete mode 100644 x-pack/test/kerberos_api_integration/apis/index.ts delete mode 100644 x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/kerberos_api_integration/services.ts delete mode 100644 x-pack/test/oidc_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/oidc_api_integration/services.ts delete mode 100644 x-pack/test/pki_api_integration/apis/index.ts delete mode 100644 x-pack/test/pki_api_integration/ftr_provider_context.d.ts delete mode 100644 x-pack/test/pki_api_integration/services.ts rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/README.md (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/kerberos_tools.ts (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/krb5.conf (100%) rename x-pack/test/{kerberos_api_integration/fixtures => security_api_integration/fixtures/kerberos}/krb5.keytab (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/README.md (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks.json (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks_private.pem (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/jwks_public.pem (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/kibana.json (100%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/server/index.ts (85%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_provider/server/init_routes.ts (98%) rename x-pack/test/{oidc_api_integration/fixtures => security_api_integration/fixtures/oidc}/oidc_tools.ts (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/README.md (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/first_client.p12 (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/kibana_ca.crt (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/kibana_ca.key (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/second_client.p12 (100%) rename x-pack/test/{pki_api_integration/fixtures => security_api_integration/fixtures/pki}/untrusted_client.p12 (100%) rename x-pack/test/{kerberos_api_integration/config.ts => security_api_integration/kerberos.config.ts} (82%) rename x-pack/test/{kerberos_api_integration/anonymous_access.config.ts => security_api_integration/kerberos_anonymous_access.config.ts} (87%) rename x-pack/test/{oidc_api_integration/config.ts => security_api_integration/oidc.config.ts} (89%) rename x-pack/test/{oidc_api_integration/implicit_flow.config.ts => security_api_integration/oidc_implicit_flow.config.ts} (88%) rename x-pack/test/{pki_api_integration/config.ts => security_api_integration/pki.config.ts} (93%) rename x-pack/test/{kerberos_api_integration/apis/security => security_api_integration/tests/kerberos}/index.ts (84%) rename x-pack/test/{kerberos_api_integration/apis/security => security_api_integration/tests/kerberos}/kerberos_login.ts (95%) rename x-pack/test/{oidc_api_integration/apis/implicit_flow => security_api_integration/tests/oidc/authorization_code_flow}/index.ts (73%) rename x-pack/test/{oidc_api_integration/apis => security_api_integration/tests/oidc}/authorization_code_flow/oidc_auth.ts (99%) rename x-pack/test/{oidc_api_integration/apis/authorization_code_flow => security_api_integration/tests/oidc/implicit_flow}/index.ts (74%) rename x-pack/test/{oidc_api_integration/apis => security_api_integration/tests/oidc}/implicit_flow/oidc_auth.ts (97%) rename x-pack/test/{pki_api_integration/apis/security => security_api_integration/tests/pki}/index.ts (85%) rename x-pack/test/{pki_api_integration/apis/security => security_api_integration/tests/pki}/pki_auth.ts (90%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2d70f6a97eed2..84076f4c4fbe9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -254,9 +254,6 @@ /x-pack/test/ui_capabilities/ @elastic/kibana-security /x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security /x-pack/test/functional/apps/security/ @elastic/kibana-security -/x-pack/test/kerberos_api_integration/ @elastic/kibana-security -/x-pack/test/oidc_api_integration/ @elastic/kibana-security -/x-pack/test/pki_api_integration/ @elastic/kibana-security /x-pack/test/security_api_integration/ @elastic/kibana-security /x-pack/test/security_functional/ @elastic/kibana-security /x-pack/test/spaces_api_integration/ @elastic/kibana-security diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 4ea395e7b53de..af26d1e60414a 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -346,6 +346,16 @@ describe('KerberosAuthenticationProvider', () => { expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); + it('does not start SPNEGO for Ajax requests.', async () => { + const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.notHandled() + ); + + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + it('succeeds if state contains a valid token.', async () => { const user = mockAuthenticatedUser(); const request = httpServerMock.createKibanaRequest({ headers: {} }); @@ -442,9 +452,6 @@ describe('KerberosAuthenticationProvider', () => { }); it('fails with `Negotiate` challenge if both access and refresh tokens from the state are expired and backend supports Kerberos.', async () => { - const request = httpServerMock.createKibanaRequest(); - const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' }; - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( new (errors.AuthenticationException as any)('Unauthorized', { body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, @@ -456,37 +463,45 @@ describe('KerberosAuthenticationProvider', () => { mockOptions.tokens.refresh.mockResolvedValue(null); - await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( + const nonAjaxRequest = httpServerMock.createKibanaRequest(); + const nonAjaxTokenPair = { + accessToken: 'expired-token', + refreshToken: 'some-valid-refresh-token', + }; + await expect(provider.authenticate(nonAjaxRequest, nonAjaxTokenPair)).resolves.toEqual( AuthenticationResult.failed(failureReason, { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); - expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); - }); - - it('does not re-start SPNEGO if both access and refresh tokens from the state are expired.', async () => { - const request = httpServerMock.createKibanaRequest({ routeAuthRequired: false }); - const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' }; - - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { - body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, + const ajaxRequest = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }); + const ajaxTokenPair = { + accessToken: 'expired-token', + refreshToken: 'ajax-some-valid-refresh-token', + }; + await expect(provider.authenticate(ajaxRequest, ajaxTokenPair)).resolves.toEqual( + AuthenticationResult.failed(failureReason, { + authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.tokens.refresh.mockResolvedValue(null); - - await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( - AuthenticationResult.notHandled() + const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false }); + const optionalAuthTokenPair = { + accessToken: 'expired-token', + refreshToken: 'optional-some-valid-refresh-token', + }; + await expect( + provider.authenticate(optionalAuthRequest, optionalAuthTokenPair) + ).resolves.toEqual( + AuthenticationResult.failed(failureReason, { + authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, + }) ); - expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(3); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(nonAjaxTokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(ajaxTokenPair.refreshToken); + expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(optionalAuthTokenPair.refreshToken); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 34ed9ac920e93..fa578b9dca45f 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -13,6 +13,7 @@ import { import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; +import { canRedirectRequest } from '../can_redirect_request'; import { Tokens, TokenPair } from '../tokens'; import { BaseAuthenticationProvider } from './base'; @@ -32,8 +33,9 @@ const WWWAuthenticateHeaderName = 'WWW-Authenticate'; * @param request Request instance. */ function canStartNewSession(request: KibanaRequest) { - // We should try to establish new session only if request requires authentication. - return request.route.options.authRequired === true; + // We should try to establish new session only if request requires authentication and it's not an XHR request. + // Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally. + return canRedirectRequest(request) && request.route.options.authRequired === true; } /** @@ -75,11 +77,8 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.notHandled(); } - let authenticationResult = authorizationHeader - ? await this.authenticateWithNegotiateScheme(request) - : AuthenticationResult.notHandled(); - - if (state && authenticationResult.notHandled()) { + let authenticationResult = AuthenticationResult.notHandled(); + if (state) { authenticationResult = await this.authenticateViaState(request, state); if ( authenticationResult.failed() && @@ -89,11 +88,15 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { } } - // If we couldn't authenticate by means of all methods above, let's try to check if Elasticsearch can - // start authentication mechanism negotiation, otherwise just return authentication result we have. - return authenticationResult.notHandled() && canStartNewSession(request) - ? await this.authenticateViaSPNEGO(request, state) - : authenticationResult; + if (!authenticationResult.notHandled() || !canStartNewSession(request)) { + return authenticationResult; + } + + // If we couldn't authenticate by means of all methods above, let's check if we're already at the authentication + // mechanism negotiation stage, otherwise check with Elasticsearch if we can start it. + return authorizationHeader + ? await this.authenticateWithNegotiateScheme(request) + : await this.authenticateViaSPNEGO(request, state); } /** @@ -264,12 +267,12 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.failed(err); } - // If refresh token is no longer valid, then we should clear session and renegotiate using SPNEGO. + // If refresh token is no longer valid, let's try to renegotiate new tokens using SPNEGO. We + // allow this because expired underlying token is an implementation detail and Kibana user + // facing session is still valid. if (refreshedTokenPair === null) { - this.logger.debug('Both access and refresh tokens are expired.'); - return canStartNewSession(request) - ? this.authenticateViaSPNEGO(request, state) - : AuthenticationResult.notHandled(); + this.logger.debug('Both access and refresh tokens are expired. Re-authenticating...'); + return this.authenticateViaSPNEGO(request, state); } try { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 969682b225ceb..94308ab5f2403 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -295,6 +295,22 @@ describe('PKIAuthenticationProvider', () => { expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); }); + it('does not exchange peer certificate to access token for Ajax requests.', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { 'kbn-xsrf': 'xsrf' }, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.notHandled() + ); + + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + it('fails with non-401 error if state is available, peer is authorized, but certificate is not available.', async () => { const request = httpServerMock.createKibanaRequest({ socket: getMockSocket({ authorized: true }), @@ -383,14 +399,7 @@ describe('PKIAuthenticationProvider', () => { }); it('gets a new access token even if existing token is expired.', async () => { - const user = mockAuthenticatedUser(); - const request = httpServerMock.createKibanaRequest({ - socket: getMockSocket({ - authorized: true, - peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), - }), - }); - const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + const user = mockAuthenticatedUser({ authentication_provider: { type: 'pki', name: 'pki' } }); const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); mockScopedClusterClient.callAsCurrentUser @@ -399,55 +408,102 @@ describe('PKIAuthenticationProvider', () => { LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) ) // In response to a call with a new token. + .mockResolvedValueOnce(user) // In response to call with an expired token. + .mockRejectedValueOnce( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ) + // In response to a call with a new token. + .mockResolvedValueOnce(user) // In response to call with an expired token. + .mockRejectedValueOnce( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ) + // In response to a call with a new token. .mockResolvedValueOnce(user); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); - await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.succeeded( - { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, - { - authHeaders: { authorization: 'Bearer access-token' }, - state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, - } - ) + const nonAjaxRequest = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + const nonAjaxState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }; + await expect(provider.authenticate(nonAjaxRequest, nonAjaxState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, + }) ); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { - body: { - x509_certificate_chain: [ - 'fingerprint:2A:7A:C2:DD:base64', - 'fingerprint:3B:8B:D3:EE:base64', - ], - }, + const ajaxRequest = httpServerMock.createKibanaRequest({ + headers: { 'kbn-xsrf': 'xsrf' }, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['3A:7A:C2:DD', '3B:8B:D3:EE']), + }), }); + const ajaxState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '3A:7A:C2:DD', + }; + await expect(provider.authenticate(ajaxRequest, ajaxState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '3A:7A:C2:DD' }, + }) + ); - expect(request.headers).not.toHaveProperty('authorization'); - }); - - it('does not exchange peer certificate to a new access token even if existing token is expired and request does not require authentication.', async () => { - const request = httpServerMock.createKibanaRequest({ + const optionalAuthRequest = httpServerMock.createKibanaRequest({ routeAuthRequired: false, socket: getMockSocket({ authorized: true, - peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + peerCertificate: getMockPeerCertificate(['4A:7A:C2:DD', '3B:8B:D3:EE']), }), }); - const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + const optionalAuthState = { + accessToken: 'existing-token', + peerCertificateFingerprint256: '4A:7A:C2:DD', + }; + await expect(provider.authenticate(optionalAuthRequest, optionalAuthState)).resolves.toEqual( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Bearer access-token' }, + state: { accessToken: 'access-token', peerCertificateFingerprint256: '4A:7A:C2:DD' }, + }) ); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - await expect(provider.authenticate(request, state)).resolves.toEqual( - AuthenticationResult.notHandled() - ); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(3); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:3A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:4A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); - expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); - expect(request.headers).not.toHaveProperty('authorization'); + expect(nonAjaxRequest.headers).not.toHaveProperty('authorization'); + expect(ajaxRequest.headers).not.toHaveProperty('authorization'); + expect(optionalAuthRequest.headers).not.toHaveProperty('authorization'); }); it('fails with 401 if existing token is expired, but certificate is not present.', async () => { diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 9214a025484fe..3629a0ac34f02 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -10,6 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; +import { canRedirectRequest } from '../can_redirect_request'; import { Tokens } from '../tokens'; import { BaseAuthenticationProvider } from './base'; @@ -33,8 +34,9 @@ interface ProviderState { * @param request Request instance. */ function canStartNewSession(request: KibanaRequest) { - // We should try to establish new session only if request requires authentication. - return request.route.options.authRequired === true; + // We should try to establish new session only if request requires authentication and it's not an XHR request. + // Technically we can authenticate XHR requests too, but we don't want these to create a new session unintentionally. + return canRedirectRequest(request) && request.route.options.authRequired === true; } /** @@ -75,12 +77,14 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { authenticationResult = await this.authenticateViaState(request, state); // If access token expired or doesn't match to the certificate fingerprint we should try to get - // a new one in exchange to peer certificate chain assuming request can initiate new session. + // a new one in exchange to peer certificate chain. Since we know that we had a valid session + // before we can safely assume that it's desired to automatically re-create session even for XHR + // requests. const invalidAccessToken = authenticationResult.notHandled() || (authenticationResult.failed() && Tokens.isAccessTokenExpiredError(authenticationResult.error)); - if (invalidAccessToken && canStartNewSession(request)) { + if (invalidAccessToken) { authenticationResult = await this.authenticateViaPeerCertificate(request); // If we have an active session that we couldn't use to authenticate user and at the same time // we couldn't use peer's certificate to establish a new one, then we should respond with 401 @@ -88,14 +92,12 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { if (authenticationResult.notHandled()) { return AuthenticationResult.failed(Boom.unauthorized()); } - } else if (invalidAccessToken) { - return AuthenticationResult.notHandled(); } } - // If we couldn't authenticate by means of all methods above, let's try to check if we can authenticate - // request using its peer certificate chain, otherwise just return authentication result we have. - // We shouldn't establish new session if authentication isn't required for this particular request. + // If we couldn't authenticate by means of all methods above, let's check if the request is allowed + // to start a new session, and if so try to authenticate request using its peer certificate chain, + // otherwise just return authentication result we have. return authenticationResult.notHandled() && canStartNewSession(request) ? await this.authenticateViaPeerCertificate(request) : authenticationResult; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 1a7c0d52cad5c..5e877717fd21e 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -32,19 +32,19 @@ const onlyNotInCoverageTests = [ require.resolve('../test/detection_engine_api_integration/basic/config.ts'), require.resolve('../test/lists_api_integration/security_and_spaces/config.ts'), require.resolve('../test/plugin_api_integration/config.ts'), - require.resolve('../test/kerberos_api_integration/config.ts'), - require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), require.resolve('../test/security_api_integration/saml.config.ts'), require.resolve('../test/security_api_integration/session_idle.config.ts'), require.resolve('../test/security_api_integration/session_lifespan.config.ts'), require.resolve('../test/security_api_integration/login_selector.config.ts'), require.resolve('../test/security_api_integration/audit.config.ts'), + require.resolve('../test/security_api_integration/kerberos.config.ts'), + require.resolve('../test/security_api_integration/kerberos_anonymous_access.config.ts'), + require.resolve('../test/security_api_integration/pki.config.ts'), + require.resolve('../test/security_api_integration/oidc.config.ts'), + require.resolve('../test/security_api_integration/oidc_implicit_flow.config.ts'), require.resolve('../test/token_api_integration/config.js'), - require.resolve('../test/oidc_api_integration/config.ts'), - require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), require.resolve('../test/observability_api_integration/basic/config.ts'), require.resolve('../test/observability_api_integration/trial/config.ts'), - require.resolve('../test/pki_api_integration/config.ts'), require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'), require.resolve('../test/spaces_api_integration/spaces_only/config.ts'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'), diff --git a/x-pack/test/kerberos_api_integration/apis/index.ts b/x-pack/test/kerberos_api_integration/apis/index.ts deleted file mode 100644 index 17da3ea7acc8d..0000000000000 --- a/x-pack/test/kerberos_api_integration/apis/index.ts +++ /dev/null @@ -1,14 +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 { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis Kerberos', function () { - this.tags('ciGroup6'); - loadTestFile(require.resolve('./security')); - }); -} diff --git a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts b/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/kerberos_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/kerberos_api_integration/services.ts b/x-pack/test/kerberos_api_integration/services.ts deleted file mode 100644 index dadae9c331a46..0000000000000 --- a/x-pack/test/kerberos_api_integration/services.ts +++ /dev/null @@ -1,15 +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 { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - legacyEs: apiIntegrationServices.legacyEs, - esSupertest: apiIntegrationServices.esSupertest, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts b/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/oidc_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/oidc_api_integration/services.ts b/x-pack/test/oidc_api_integration/services.ts deleted file mode 100644 index e2abfa71451bc..0000000000000 --- a/x-pack/test/oidc_api_integration/services.ts +++ /dev/null @@ -1,14 +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 { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - legacyEs: apiIntegrationServices.legacyEs, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/pki_api_integration/apis/index.ts b/x-pack/test/pki_api_integration/apis/index.ts deleted file mode 100644 index 01b537fc07d1b..0000000000000 --- a/x-pack/test/pki_api_integration/apis/index.ts +++ /dev/null @@ -1,14 +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 { FtrProviderContext } from '../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis PKI', function () { - this.tags('ciGroup6'); - loadTestFile(require.resolve('./security')); - }); -} diff --git a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts b/x-pack/test/pki_api_integration/ftr_provider_context.d.ts deleted file mode 100644 index e3add3748f56d..0000000000000 --- a/x-pack/test/pki_api_integration/ftr_provider_context.d.ts +++ /dev/null @@ -1,11 +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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/pki_api_integration/services.ts b/x-pack/test/pki_api_integration/services.ts deleted file mode 100644 index 73ec6fe396392..0000000000000 --- a/x-pack/test/pki_api_integration/services.ts +++ /dev/null @@ -1,14 +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 { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; - -export const services = { - ...commonServices, - esSupertest: apiIntegrationServices.esSupertest, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, -}; diff --git a/x-pack/test/kerberos_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/kerberos/README.md similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/kerberos/README.md diff --git a/x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts b/x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/kerberos_tools.ts rename to x-pack/test/security_api_integration/fixtures/kerberos/kerberos_tools.ts diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.conf b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.conf rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.conf diff --git a/x-pack/test/kerberos_api_integration/fixtures/krb5.keytab b/x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab similarity index 100% rename from x-pack/test/kerberos_api_integration/fixtures/krb5.keytab rename to x-pack/test/security_api_integration/fixtures/kerberos/krb5.keytab diff --git a/x-pack/test/oidc_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/oidc/README.md similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/oidc/README.md diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks.json b/x-pack/test/security_api_integration/fixtures/oidc/jwks.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks.json rename to x-pack/test/security_api_integration/fixtures/oidc/jwks.json diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_private.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_private.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_private.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/jwks_public.pem b/x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/jwks_public.pem rename to x-pack/test/security_api_integration/fixtures/oidc/jwks_public.pem diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/kibana.json rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/kibana.json diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts similarity index 85% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts index 58401e725830f..082fec55c3413 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/index.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializer } from '../../../../../../src/core/server'; +import type { PluginInitializer } from '../../../../../../../src/core/server'; import { initRoutes } from './init_routes'; export const plugin: PluginInitializer = () => ({ diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts similarity index 98% rename from x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts index 73f92139806e3..8f75246d995c3 100644 --- a/x-pack/test/oidc_api_integration/fixtures/oidc_provider/server/init_routes.ts +++ b/x-pack/test/security_api_integration/fixtures/oidc/oidc_provider/server/init_routes.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../../../../../src/core/server'; +import type { IRouter } from '../../../../../../../src/core/server'; import { createTokens } from '../../oidc_tools'; export function initRoutes(router: IRouter) { diff --git a/x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts b/x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts similarity index 100% rename from x-pack/test/oidc_api_integration/fixtures/oidc_tools.ts rename to x-pack/test/security_api_integration/fixtures/oidc/oidc_tools.ts diff --git a/x-pack/test/pki_api_integration/fixtures/README.md b/x-pack/test/security_api_integration/fixtures/pki/README.md similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/README.md rename to x-pack/test/security_api_integration/fixtures/pki/README.md diff --git a/x-pack/test/pki_api_integration/fixtures/first_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/first_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/first_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/first_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.crt b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.crt rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.crt diff --git a/x-pack/test/pki_api_integration/fixtures/kibana_ca.key b/x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/kibana_ca.key rename to x-pack/test/security_api_integration/fixtures/pki/kibana_ca.key diff --git a/x-pack/test/pki_api_integration/fixtures/second_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/second_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/second_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/second_client.p12 diff --git a/x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 b/x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 similarity index 100% rename from x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 rename to x-pack/test/security_api_integration/fixtures/pki/untrusted_client.p12 diff --git a/x-pack/test/kerberos_api_integration/config.ts b/x-pack/test/security_api_integration/kerberos.config.ts similarity index 82% rename from x-pack/test/kerberos_api_integration/config.ts rename to x-pack/test/security_api_integration/kerberos.config.ts index 7b65d79e18e7d..08c2007989083 100644 --- a/x-pack/test/kerberos_api_integration/config.ts +++ b/x-pack/test/security_api_integration/kerberos.config.ts @@ -11,21 +11,15 @@ import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); - const kerberosKeytabPath = resolve( - __dirname, - '../../test/kerberos_api_integration/fixtures/krb5.keytab' - ); - const kerberosConfigPath = resolve( - __dirname, - '../../test/kerberos_api_integration/fixtures/krb5.conf' - ); + const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab'); + const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf'); return { - testFiles: [require.resolve('./apis')], + testFiles: [require.resolve('./tests/kerberos')], servers: xPackAPITestsConfig.get('servers'), services, junit: { - reportName: 'X-Pack Kerberos API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (Kerberos)', }, esTestCluster: { diff --git a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts similarity index 87% rename from x-pack/test/kerberos_api_integration/anonymous_access.config.ts rename to x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts index 17362831a6cd0..6621b536c7ca9 100644 --- a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts +++ b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts @@ -7,13 +7,13 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const kerberosAPITestsConfig = await readConfigFile(require.resolve('./config.ts')); + const kerberosAPITestsConfig = await readConfigFile(require.resolve('./kerberos.config.ts')); return { ...kerberosAPITestsConfig.getAll(), junit: { - reportName: 'X-Pack Kerberos API with Anonymous Access Integration Tests', + reportName: 'X-Pack Security API Integration Tests (Kerberos with Anonymous Access)', }, esTestCluster: { diff --git a/x-pack/test/security_api_integration/login_selector.config.ts b/x-pack/test/security_api_integration/login_selector.config.ts index 0e43715ba808e..9688d42cb4361 100644 --- a/x-pack/test/security_api_integration/login_selector.config.ts +++ b/x-pack/test/security_api_integration/login_selector.config.ts @@ -15,13 +15,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); - const kerberosKeytabPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.keytab'); - const kerberosConfigPath = resolve(__dirname, '../kerberos_api_integration/fixtures/krb5.conf'); + const kerberosKeytabPath = resolve(__dirname, './fixtures/kerberos/krb5.keytab'); + const kerberosConfigPath = resolve(__dirname, './fixtures/kerberos/krb5.conf'); - const oidcJWKSPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json'); - const oidcIdPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider'); + const oidcJWKSPath = resolve(__dirname, './fixtures/oidc/jwks.json'); + const oidcIdPPlugin = resolve(__dirname, './fixtures/oidc/oidc_provider'); - const pkiKibanaCAPath = resolve(__dirname, '../pki_api_integration/fixtures/kibana_ca.crt'); + const pkiKibanaCAPath = resolve(__dirname, './fixtures/pki/kibana_ca.crt'); const saml1IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); const saml2IdPMetadataPath = resolve(__dirname, './fixtures/saml/idp_metadata_2.xml'); diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/security_api_integration/oidc.config.ts similarity index 89% rename from x-pack/test/oidc_api_integration/config.ts rename to x-pack/test/security_api_integration/oidc.config.ts index 08aa0a6d9c0dd..cb92282b40d32 100644 --- a/x-pack/test/oidc_api_integration/config.ts +++ b/x-pack/test/security_api_integration/oidc.config.ts @@ -10,17 +10,17 @@ import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); - const plugin = resolve(__dirname, './fixtures/oidc_provider'); + const plugin = resolve(__dirname, './fixtures/oidc/oidc_provider'); const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); - const jwksPath = resolve(__dirname, './fixtures/jwks.json'); + const jwksPath = resolve(__dirname, './fixtures/oidc/jwks.json'); return { - testFiles: [require.resolve('./apis/authorization_code_flow')], + testFiles: [require.resolve('./tests/oidc/authorization_code_flow')], servers: xPackAPITestsConfig.get('servers'), security: { disableTestUser: true }, services, junit: { - reportName: 'X-Pack OpenID Connect API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (OIDC - Authorization Code Flow)', }, esTestCluster: { diff --git a/x-pack/test/oidc_api_integration/implicit_flow.config.ts b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts similarity index 88% rename from x-pack/test/oidc_api_integration/implicit_flow.config.ts rename to x-pack/test/security_api_integration/oidc_implicit_flow.config.ts index 992115d05c5a8..8907998f4df6d 100644 --- a/x-pack/test/oidc_api_integration/implicit_flow.config.ts +++ b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts @@ -7,14 +7,14 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const oidcAPITestsConfig = await readConfigFile(require.resolve('./config.ts')); + const oidcAPITestsConfig = await readConfigFile(require.resolve('./oidc.config.ts')); return { ...oidcAPITestsConfig.getAll(), - testFiles: [require.resolve('./apis/implicit_flow')], + testFiles: [require.resolve('./tests/oidc/implicit_flow')], junit: { - reportName: 'X-Pack OpenID Connect API Integration Tests (Implicit Flow)', + reportName: 'X-Pack Security API Integration Tests (OIDC - Implicit Flow)', }, esTestCluster: { diff --git a/x-pack/test/pki_api_integration/config.ts b/x-pack/test/security_api_integration/pki.config.ts similarity index 93% rename from x-pack/test/pki_api_integration/config.ts rename to x-pack/test/security_api_integration/pki.config.ts index 5ce3111530dd9..1ce8bf9971fe0 100644 --- a/x-pack/test/pki_api_integration/config.ts +++ b/x-pack/test/security_api_integration/pki.config.ts @@ -25,12 +25,12 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }; return { - testFiles: [require.resolve('./apis')], + testFiles: [require.resolve('./tests/pki')], servers, security: { disableTestUser: true }, services, junit: { - reportName: 'X-Pack PKI API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (PKI)', }, esTestCluster: { @@ -58,7 +58,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--server.ssl.certificate=${KBN_CERT_PATH}`, `--server.ssl.certificateAuthorities=${JSON.stringify([ CA_CERT_PATH, - resolve(__dirname, './fixtures/kibana_ca.crt'), + resolve(__dirname, './fixtures/pki/kibana_ca.crt'), ])}`, `--server.ssl.clientAuthentication=required`, `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/security_api_integration/services.ts b/x-pack/test/security_api_integration/services.ts index a8d8048462693..73ec6fe396392 100644 --- a/x-pack/test/security_api_integration/services.ts +++ b/x-pack/test/security_api_integration/services.ts @@ -9,5 +9,6 @@ import { services as apiIntegrationServices } from '../api_integration/services' export const services = { ...commonServices, + esSupertest: apiIntegrationServices.esSupertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/kerberos_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/kerberos/index.ts similarity index 84% rename from x-pack/test/kerberos_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/kerberos/index.ts index 77a053ab14748..3fa2d155353a7 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/index.ts @@ -7,7 +7,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('security', () => { + describe('security APIs - Kerberos', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./kerberos_login')); }); } diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts similarity index 95% rename from x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts rename to x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index c31f6b689e972..e63f8cd2ebe32 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, -} from '../../fixtures/kerberos_tools'; +} from '../../fixtures/kerberos/kerberos_tools'; export default function ({ getService }: FtrProviderContext) { const spnegoToken = getSPNEGOToken(); @@ -92,21 +92,21 @@ export default function ({ getService }: FtrProviderContext) { expect(spnegoResponse.headers['www-authenticate']).to.be('Negotiate'); }); - it('AJAX requests should properly initiate SPNEGO', async () => { + it('AJAX requests should not initiate SPNEGO', async () => { const ajaxResponse = await supertest .get('/abc/xyz/spnego?one=two three') .set('kbn-xsrf', 'xxx') .expect(401); expect(ajaxResponse.headers['set-cookie']).to.be(undefined); - expect(ajaxResponse.headers['www-authenticate']).to.be('Negotiate'); + expect(ajaxResponse.headers['www-authenticate']).to.be(undefined); }); }); describe('finishing SPNEGO', () => { it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { it('should re-initiate SPNEGO handshake if token is rejected with 401', async () => { const spnegoResponse = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${Buffer.from('Hello').toString('base64')}`) .expect(401); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { it('should fail if SPNEGO token is rejected because of unknown reason', async () => { const spnegoResponse = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', 'Negotiate (:I am malformed:)') .expect(500); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -175,7 +175,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -239,7 +239,7 @@ export default function ({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -274,7 +274,9 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(apiResponse.headers['www-authenticate']).to.be('Negotiate'); + // Request with a session cookie that is linked to an invalidated/non-existent session is treated the same as + // request without any session cookie at all. + expect(apiResponse.headers['www-authenticate']).to.be(undefined); }); it('should redirect to home page if session cookie is not provided', async () => { @@ -290,7 +292,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -342,7 +344,7 @@ export default function ({ getService }: FtrProviderContext) { // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const nonAjaxResponse = await supertest - .get('/app/kibana') + .get('/security/account') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -368,7 +370,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -405,7 +407,7 @@ export default function ({ getService }: FtrProviderContext) { it('non-AJAX call should initiate SPNEGO and clear existing cookie', async function () { const nonAjaxResponse = await supertest - .get('/') + .get('/security/account') .set('Cookie', sessionCookie.cookieString()) .expect(401); diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 432fd6ff91280..cf141972b044a 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -11,11 +11,11 @@ import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; -import { getStateAndNonce } from '../../../oidc_api_integration/fixtures/oidc_tools'; +import { getStateAndNonce } from '../../fixtures/oidc/oidc_tools'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, -} from '../../../kerberos_api_integration/fixtures/kerberos_tools'; +} from '../../fixtures/kerberos/kerberos_tools'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -29,9 +29,7 @@ export default function ({ getService }: FtrProviderContext) { const validPassword = kibanaServerConfig.password; const CA_CERT = readFileSync(CA_CERT_PATH); - const CLIENT_CERT = readFileSync( - resolve(__dirname, '../../../pki_api_integration/fixtures/first_client.p12') - ); + const CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12')); async function checkSessionCookie( sessionCookie: Cookie, diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts similarity index 73% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts index 0acae074f129f..4def5388abae0 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis', function () { + describe('security APIs - OIDC (Authorization Code Flow)', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./oidc_auth')); }); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts similarity index 99% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index 1fdb15a86ce0a..aac41374734b2 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import request, { Cookie } from 'request'; import url from 'url'; import { delay } from 'bluebird'; -import { getStateAndNonce } from '../../fixtures/oidc_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts similarity index 74% rename from x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts index 0acae074f129f..0441d14b9196d 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('apis', function () { + describe('security APIs - OIDC (Implicit Flow)', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./oidc_auth')); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts similarity index 97% rename from x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts rename to x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts index 7c408d8b903e3..ced9598809e10 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import { JSDOM } from 'jsdom'; import request, { Cookie } from 'request'; import { format as formatURL } from 'url'; -import { createTokens, getStateAndNonce } from '../../fixtures/oidc_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { createTokens, getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/pki_api_integration/apis/security/index.ts b/x-pack/test/security_api_integration/tests/pki/index.ts similarity index 85% rename from x-pack/test/pki_api_integration/apis/security/index.ts rename to x-pack/test/security_api_integration/tests/pki/index.ts index 63dca75d075fa..380335ba25f84 100644 --- a/x-pack/test/pki_api_integration/apis/security/index.ts +++ b/x-pack/test/security_api_integration/tests/pki/index.ts @@ -7,7 +7,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('security', () => { + describe('security APIs - PKI', function () { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./pki_auth')); }); } diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts similarity index 90% rename from x-pack/test/pki_api_integration/apis/security/pki_auth.ts rename to x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 43b728d12311d..0331f756712ca 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -13,10 +13,10 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrProviderContext } from '../../ftr_provider_context'; const CA_CERT = readFileSync(CA_CERT_PATH); -const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/first_client.p12')); -const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/second_client.p12')); +const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/first_client.p12')); +const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/pki/second_client.p12')); const UNTRUSTED_CLIENT_CERT = readFileSync( - resolve(__dirname, '../../fixtures/untrusted_client.p12') + resolve(__dirname, '../../fixtures/pki/untrusted_client.p12') ); export default function ({ getService }: FtrProviderContext) { @@ -97,9 +97,20 @@ export default function ({ getService }: FtrProviderContext) { // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); + it('AJAX requests should not create a new session', async () => { + const ajaxResponse = await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(401); + + expect(ajaxResponse.headers['set-cookie']).to.be(undefined); + }); + it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -110,35 +121,34 @@ export default function ({ getService }: FtrProviderContext) { const sessionCookie = request.cookie(cookies[0])!; checkCookieIsSet(sessionCookie); - expect(response.body).to.eql({ - username: 'first_client', - roles: ['kibana_admin'], - full_name: null, - email: null, - enabled: true, - metadata: { - pki_delegated_by_realm: 'reserved', - pki_delegated_by_user: 'kibana', - pki_dn: 'CN=first_client', - }, - authentication_realm: { name: 'pki1', type: 'pki' }, - lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: { name: 'pki', type: 'pki' }, - authentication_type: 'token', - }); - // Cookie should be accepted. await supertest .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) - .expect(200); + .expect(200, { + username: 'first_client', + roles: ['kibana_admin'], + full_name: null, + email: null, + enabled: true, + metadata: { + pki_delegated_by_realm: 'reserved', + pki_delegated_by_user: 'kibana', + pki_dn: 'CN=first_client', + }, + authentication_realm: { name: 'pki1', type: 'pki' }, + lookup_realm: { name: 'pki1', type: 'pki' }, + authentication_provider: { name: 'pki', type: 'pki' }, + authentication_type: 'token', + }); }); it('should update session if new certificate is provided', async () => { let response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -177,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) { it('should reject valid cookie if used with untrusted certificate', async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -201,7 +211,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -274,7 +284,7 @@ export default function ({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -317,7 +327,7 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/internal/security/me') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -363,7 +373,7 @@ export default function ({ getService }: FtrProviderContext) { // This request should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const nonAjaxResponse = await supertest - .get('/app/kibana') + .get('/security/account') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) diff --git a/x-pack/test/security_functional/oidc.config.ts b/x-pack/test/security_functional/oidc.config.ts index 1ed5d51098420..add6f0f164b3a 100644 --- a/x-pack/test/security_functional/oidc.config.ts +++ b/x-pack/test/security_functional/oidc.config.ts @@ -20,8 +20,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const kibanaPort = kibanaFunctionalConfig.get('servers.kibana.port'); - const jwksPath = resolve(__dirname, '../oidc_api_integration/fixtures/jwks.json'); - const oidcOpPPlugin = resolve(__dirname, '../oidc_api_integration/fixtures/oidc_provider'); + const jwksPath = resolve(__dirname, '../security_api_integration/fixtures/oidc/jwks.json'); + const oidcOpPPlugin = resolve( + __dirname, + '../security_api_integration/fixtures/oidc/oidc_provider' + ); return { testFiles: [resolve(__dirname, './tests/oidc')], From 2c059575824c4aba9e49189d7ccbc89f10aa07b2 Mon Sep 17 00:00:00 2001 From: DeFazio Date: Mon, 9 Nov 2020 11:38:39 -0500 Subject: [PATCH 23/87] Update alert type selection layout to rows instead of grid (#73665) * Update layout to rows for alert types * Fix gutter usage * Update heading, remove icons * Non-working update to the combo box * Add incorrect updates with questions to fix * Fix combo box * Cleanup changes to specific to this module * fixed type checks and made combobox always visible * Added groups by producer * Added get producer name from kibana features names * Added search bar with list of alert types * Added search support functionality * fixed links to alert type * added alert type title * Fixed failing tests * Design updates to list * Remove unsed items in import list * fixed merge issue * Fixed due to comments * fixed tests * Design fixes Co-authored-by: Yuliia Naumenko --- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../public/application/app.tsx | 2 +- .../application/context/alerts_context.tsx | 2 + .../sections/alert_form/alert_add.test.tsx | 2 - .../sections/alert_form/alert_form.scss | 4 + .../sections/alert_form/alert_form.test.tsx | 4 +- .../sections/alert_form/alert_form.tsx | 293 ++++++++++++++---- .../sections/alert_form/solution_filter.tsx | 73 +++++ .../alerts_list/components/alerts_list.tsx | 1 + .../triggers_actions_ui/public/plugin.ts | 1 - 11 files changed, 318 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 485b24dced346..d67cc06463942 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20148,7 +20148,6 @@ "xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター", "xpack.triggersActionsUI.home.sectionDescription": "アラートを使用して条件を検出し、コネクターを使用してアクションを実行します。", "xpack.triggersActionsUI.managementSection.displayName": "アラートとアクション", - "xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "タグ (任意)", "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "ライセンスの管理", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存してテスト", @@ -20256,7 +20255,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "コネクターを作成する", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新規追加", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名前", - "xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "削除", "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "確認間隔", "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "条件を評価する頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "{actionTypeName}コネクターがありません", @@ -20273,7 +20271,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください", - "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "トリガータイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "コネクターを読み込めません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 98d13011d3306..103aef656c4e1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20167,7 +20167,6 @@ "xpack.triggersActionsUI.home.connectorsTabTitle": "连接器", "xpack.triggersActionsUI.home.sectionDescription": "使用告警检测条件,并使用连接器采取操作。", "xpack.triggersActionsUI.managementSection.displayName": "告警和操作", - "xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel": "标记(可选)", "xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.actionConnectorAdd.manageLicensePlanBannerLinkTitle": "管理许可证", "xpack.triggersActionsUI.sections.actionConnectorAdd.saveAndTestButtonLabel": "保存并测试", @@ -20276,7 +20275,6 @@ "xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel": "创建连接器", "xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton": "新添", "xpack.triggersActionsUI.sections.alertForm.alertNameLabel": "名称", - "xpack.triggersActionsUI.sections.alertForm.changeAlertTypeAriaLabel": "删除", "xpack.triggersActionsUI.sections.alertForm.checkFieldLabel": "检查频率", "xpack.triggersActionsUI.sections.alertForm.checkWithTooltip": "定义评估条件的频率。", "xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel": "无 {actionTypeName} 连接器", @@ -20293,7 +20291,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型", - "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "选择触发器类型", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "无法加载连接器", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index fc48a8e977c7d..5c1e0aa0100e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -16,8 +16,8 @@ import { CoreStart, ScopedHistory, } from 'kibana/public'; -import { Section, routeToAlertDetails } from './constants'; import { KibanaFeature } from '../../../features/common'; +import { Section, routeToAlertDetails } from './constants'; import { AppContextProvider } from './app_context'; import { ActionTypeRegistryContract, AlertTypeRegistryContract } from '../types'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index a4293f94268ba..0b2f777d13f25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -18,6 +18,7 @@ import { DataPublicPluginStartUi, IndexPatternsContract, } from 'src/plugins/data/public'; +import { KibanaFeature } from '../../../../features/common'; import { AlertTypeRegistryContract, ActionTypeRegistryContract } from '../../types'; export interface AlertsContextValue> { @@ -34,6 +35,7 @@ export interface AlertsContextValue> { metadata?: MetaData; dataUi?: DataPublicPluginStartUi; dataIndexPatterns?: IndexPatternsContract; + kibanaFeatures?: KibanaFeature[]; } const AlertsContext = createContext(null as any); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index d66c5ba5121b8..4b5f8596501e1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -182,8 +182,6 @@ describe('alert_add', () => { wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); - expect(wrapper.contains('Metadata: some value. Fields: test.')).toBeTruthy(); - expect(wrapper.find('input#alertName').props().value).toBe(''); expect(wrapper.find('[data-test-subj="tagsComboBox"]').first().text()).toBe(''); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss new file mode 100644 index 0000000000000..5d6ac684002fb --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.scss @@ -0,0 +1,4 @@ +.triggersActionsUI__alertTypeNodeHeading { + margin-left: $euiSizeS; + margin-right: $euiSizeS; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 4041f6f451a23..493b870a1a6d5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -187,7 +187,7 @@ describe('alert_form', () => { it('renders alert type description', async () => { await setup(); - wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); const alertDescription = wrapper.find('[data-test-subj="alertDescription"]'); expect(alertDescription.exists()).toBeTruthy(); expect(alertDescription.first().text()).toContain('Alert when testing'); @@ -195,7 +195,7 @@ describe('alert_form', () => { it('renders alert type documentation link', async () => { await setup(); - wrapper.find('[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); + wrapper.find('button[data-test-subj="my-alert-type-SelectOption"]').first().simulate('click'); const alertDocumentationLink = wrapper.find('[data-test-subj="alertDocumentationLink"]'); expect(alertDocumentationLink.exists()).toBeTruthy(); expect(alertDocumentationLink.first().prop('href')).toBe('https://localhost.local/docs'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 20ad9a8d7c701..213d1d7ad36df 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -9,15 +9,15 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiIcon, + EuiTextColor, EuiTitle, EuiForm, EuiSpacer, EuiFieldText, + EuiFieldSearch, EuiFlexGrid, EuiFormRow, EuiComboBox, - EuiKeyPadMenuItem, EuiFieldNumber, EuiSelect, EuiIconTip, @@ -25,11 +25,16 @@ import { EuiHorizontalRule, EuiLoadingSpinner, EuiEmptyPrompt, + EuiListGroupItem, + EuiListGroup, EuiLink, EuiText, + EuiNotificationBadge, } from '@elastic/eui'; import { some, filter, map, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; +import { capitalize } from 'lodash'; +import { KibanaFeature } from '../../../../../features/public'; import { getDurationNumberInItsUnit, getDurationUnitValue, @@ -37,12 +42,23 @@ import { import { loadAlertTypes } from '../../lib/alert_api'; import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { AlertReducerAction } from './alert_reducer'; -import { AlertTypeModel, Alert, IErrorObject, AlertAction, AlertTypeIndex } from '../../../types'; +import { + AlertTypeModel, + Alert, + IErrorObject, + AlertAction, + AlertTypeIndex, + AlertType, +} from '../../../types'; import { getTimeOptions } from '../../../common/lib/get_time_options'; import { useAlertsContext } from '../../context/alerts_context'; import { ActionForm } from '../action_connector_form'; import { ALERTS_FEATURE_ID } from '../../../../../alerts/common'; import { hasAllPrivilege, hasShowActionsCapability } from '../../lib/capabilities'; +import { SolutionFilter } from './solution_filter'; +import './alert_form.scss'; + +const ENTER_KEY = 13; export function validateBaseProperties(alertObject: Alert) { const validationResult = { errors: {} }; @@ -77,6 +93,10 @@ export function validateBaseProperties(alertObject: Alert) { return validationResult; } +function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) { + return kibanaFeatures.find((featureItem) => featureItem.id === producer)?.name; +} + interface AlertFormProps { alert: Alert; dispatch: React.Dispatch; @@ -104,12 +124,12 @@ export const AlertForm = ({ actionTypeRegistry, docLinks, capabilities, + kibanaFeatures, } = alertsContext; const canShowActions = hasShowActionsCapability(capabilities); const [alertTypeModel, setAlertTypeModel] = useState(null); - const [alertTypesIndex, setAlertTypesIndex] = useState(undefined); const [alertInterval, setAlertInterval] = useState( alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : undefined ); @@ -123,20 +143,53 @@ export const AlertForm = ({ alert.throttle ? getDurationUnitValue(alert.throttle) : 'm' ); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); + const [alertTypesIndex, setAlertTypesIndex] = useState(null); + + const [availableAlertTypes, setAvailableAlertTypes] = useState< + Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }> + >([]); + const [filteredAlertTypes, setFilteredAlertTypes] = useState< + Array<{ alertTypeModel: AlertTypeModel; alertType: AlertType }> + >([]); + const [searchText, setSearchText] = useState(); + const [inputText, setInputText] = useState(); + const [solutions, setSolutions] = useState | undefined>(undefined); + const [solutionsFilter, setSolutionFilter] = useState([]); // load alert types useEffect(() => { (async () => { try { - const alertTypes = await loadAlertTypes({ http }); + const alertTypesResult = await loadAlertTypes({ http }); const index: AlertTypeIndex = new Map(); - for (const alertTypeItem of alertTypes) { + for (const alertTypeItem of alertTypesResult) { index.set(alertTypeItem.id, alertTypeItem); } if (alert.alertTypeId && index.has(alert.alertTypeId)) { setDefaultActionGroupId(index.get(alert.alertTypeId)!.defaultActionGroupId); } setAlertTypesIndex(index); + const availableAlertTypesResult = getAvailableAlertTypes(alertTypesResult); + setAvailableAlertTypes(availableAlertTypesResult); + + const solutionsResult = availableAlertTypesResult.reduce( + (result: Map, alertTypeItem) => { + if (!result.has(alertTypeItem.alertType.producer)) { + result.set( + alertTypeItem.alertType.producer, + (kibanaFeatures + ? getProducerFeatureName(alertTypeItem.alertType.producer, kibanaFeatures) + : capitalize(alertTypeItem.alertType.producer)) ?? + capitalize(alertTypeItem.alertType.producer) + ); + } + return result; + }, + new Map() + ); + setSolutions( + new Map([...solutionsResult.entries()].sort(([, a], [, b]) => a.localeCompare(b))) + ); } catch (e) { toastNotifications.addDanger({ title: i18n.translate( @@ -184,47 +237,143 @@ export const AlertForm = ({ [dispatch] ); + useEffect(() => { + const searchValue = searchText ? searchText.trim().toLocaleLowerCase() : null; + setFilteredAlertTypes( + availableAlertTypes + .filter((alertTypeItem) => + solutionsFilter.length > 0 + ? solutionsFilter.find((item) => alertTypeItem.alertType!.producer === item) + : alertTypeItem + ) + .filter((alertTypeItem) => + searchValue + ? alertTypeItem.alertTypeModel.name + .toString() + .toLocaleLowerCase() + .includes(searchValue) || + alertTypeItem.alertType!.producer.toLocaleLowerCase().includes(searchValue) || + alertTypeItem.alertTypeModel.description.toLocaleLowerCase().includes(searchValue) + : alertTypeItem + ) + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alertTypeRegistry, availableAlertTypes, searchText, JSON.stringify(solutionsFilter)]); + + const getAvailableAlertTypes = (alertTypesResult: AlertType[]) => + alertTypeRegistry + .list() + .reduce( + ( + arr: Array<{ alertType: AlertType; alertTypeModel: AlertTypeModel }>, + alertTypeRegistryItem: AlertTypeModel + ) => { + const alertType = alertTypesResult.find((item) => alertTypeRegistryItem.id === item.id); + if (alertType) { + arr.push({ + alertType, + alertTypeModel: alertTypeRegistryItem, + }); + } + return arr; + }, + [] + ) + .filter((item) => item.alertType && hasAllPrivilege(alert, item.alertType)) + .filter((item) => + alert.consumer === ALERTS_FEATURE_ID + ? !item.alertTypeModel.requiresAppContext + : item.alertType!.producer === alert.consumer + ); + const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; const AlertParamsExpressionComponent = alertTypeModel ? alertTypeModel.alertParamsExpression : null; - const alertTypeRegistryList = alertTypesIndex - ? alertTypeRegistry - .list() - .filter( - (alertTypeRegistryItem: AlertTypeModel) => - alertTypesIndex.has(alertTypeRegistryItem.id) && - hasAllPrivilege(alert, alertTypesIndex.get(alertTypeRegistryItem.id)) - ) - .filter((alertTypeRegistryItem: AlertTypeModel) => - alert.consumer === ALERTS_FEATURE_ID - ? !alertTypeRegistryItem.requiresAppContext - : alertTypesIndex.get(alertTypeRegistryItem.id)!.producer === alert.consumer - ) - : []; + const alertTypesByProducer = filteredAlertTypes.reduce( + ( + result: Record>, + alertTypeValue + ) => { + const producer = alertTypeValue.alertType.producer; + if (producer) { + (result[producer] = result[producer] || []).push({ + name: + typeof alertTypeValue.alertTypeModel.name === 'string' + ? alertTypeValue.alertTypeModel.name + : alertTypeValue.alertTypeModel.name.props.defaultMessage, + id: alertTypeValue.alertTypeModel.id, + alertTypeItem: alertTypeValue.alertTypeModel, + }); + } + return result; + }, + {} + ); - const alertTypeNodes = alertTypeRegistryList.map(function (item, index) { - return ( - { - setAlertProperty('alertTypeId', item.id); - setActions([]); - setAlertTypeModel(item); - setAlertProperty('params', {}); - if (alertTypesIndex && alertTypesIndex.has(item.id)) { - setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); - } - }} - > - - - ); - }); + const alertTypeNodes = Object.entries(alertTypesByProducer) + .sort(([a], [b]) => + solutions ? solutions.get(a)!.localeCompare(solutions.get(b)!) : a.localeCompare(b) + ) + .map(([solution, items], groupIndex) => ( + + + + + + {(kibanaFeatures + ? getProducerFeatureName(solution, kibanaFeatures) + : capitalize(solution)) ?? capitalize(solution)} + + + + + {items.length} + + + + + {items + .sort((a, b) => a.name.toString().localeCompare(b.name.toString())) + .map((item, index) => ( + + + {item.name} + +

{item.alertTypeItem.description}

+
+ + } + onClick={() => { + setAlertProperty('alertTypeId', item.id); + setActions([]); + setAlertTypeModel(item.alertTypeItem); + setAlertProperty('params', {}); + if (alertTypesIndex && alertTypesIndex.has(item.id)) { + setDefaultActionGroupId(alertTypesIndex.get(item.id)!.defaultActionGroupId); + } + }} + /> +
+ ))} +
+ +
+ )); const alertTypeDetails = ( @@ -401,12 +550,9 @@ export const AlertForm = ({ {alertTypeModel ? ( {alertTypeDetails} - ) : alertTypeNodes.length ? ( + ) : availableAlertTypes.length ? ( - -
- -
-
+ +
+ +
+ + } + > + + + setInputText(e.target.value)} + onKeyUp={(e) => { + if (e.keyCode === ENTER_KEY) { + setSearchText(inputText); + } + }} + placeholder={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.searchPlaceholderTitle', + { defaultMessage: 'Search' } + )} + /> + + {solutions ? ( + + setSolutionFilter(selectedSolutions)} + /> + + ) : null} + +
- - {alertTypeNodes} - + {alertTypeNodes}
) : alertTypesIndex ? ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx new file mode 100644 index 0000000000000..7caee22cf7633 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/solution_filter.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; + +interface SolutionFilterProps { + solutions: Map; + onChange?: (selectedSolutions: string[]) => void; +} + +export const SolutionFilter: React.FunctionComponent = ({ + solutions, + onChange, +}: SolutionFilterProps) => { + const [selectedValues, setSelectedValues] = useState([]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + if (onChange) { + onChange(selectedValues); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedValues]); + + return ( + + setIsPopoverOpen(false)} + button={ + 0} + numActiveFilters={selectedValues.length} + numFilters={selectedValues.length} + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + data-test-subj="solutionsFilterButton" + > + + + } + > +
+ {[...solutions.entries()].map(([id, title]) => ( + { + const isPreviouslyChecked = selectedValues.includes(id); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter((val) => val !== id)); + } else { + setSelectedValues([...selectedValues, id]); + } + }} + checked={selectedValues.includes(id) ? 'on' : undefined} + data-test-subj={`solution${id}FilterOption`} + > + {title} + + ))} +
+
+
+ ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 3b0cd0b177b1b..75f359888a858 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -672,6 +672,7 @@ export const AlertsList: React.FunctionComponent = () => { capabilities, dataUi: dataPlugin.ui, dataIndexPatterns: dataPlugin.indexPatterns, + kibanaFeatures, }} > Date: Mon, 9 Nov 2020 10:41:04 -0600 Subject: [PATCH 24/87] [ML] Add option for anomaly charts for metric detector should plot min, mean or max as appropriate (#81662) --- .../anomalies_table/anomaly_details.js | 4 +- .../explorer_chart_config_builder.js | 6 ++ .../explorer_charts_container_service.js | 2 - .../routing/routes/timeseriesexplorer.tsx | 3 + .../services/ml_api_service/results.ts | 6 +- .../results_service/result_service_rx.ts | 21 ++++- .../results_service/results_service.d.ts | 3 +- .../results_service/results_service.js | 21 ++++- .../plot_function_controls/index.ts | 7 ++ .../plot_function_controls.tsx | 56 +++++++++++++ .../timeseries_chart/timeseries_chart.js | 16 ++++ .../timeseriesexplorer/get_criteria_fields.ts | 24 ++++++ .../get_function_description.ts | 62 ++++++++++++++ .../timeseries_search_service.ts | 5 +- .../timeseriesexplorer/timeseriesexplorer.js | 81 +++++++++++++++---- .../get_focus_data.ts | 18 ++++- .../timeseriesexplorer_utils.d.ts | 3 +- .../timeseriesexplorer_utils.js | 14 +++- .../models/results_service/results_service.ts | 10 ++- .../ml/server/routes/results_service.ts | 4 +- .../routes/schemas/results_service_schema.ts | 1 + 21 files changed, 333 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js index a2a3aea5988aa..fdd855e80a6df 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js @@ -38,6 +38,7 @@ import { import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; import { formatValue } from '../../formatters/format_value'; import { MAX_CHARS } from './anomalies_table_constants'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; const TIME_FIELD_NAME = 'timestamp'; @@ -130,7 +131,8 @@ function getDetailsItems(anomaly, examples, filter) { title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.functionTitle', { defaultMessage: 'function', }), - description: source.function !== 'metric' ? source.function : source.function_description, + description: + source.function !== ML_JOB_AGGREGATION.METRIC ? source.function : source.function_description, }); if (source.field_name !== undefined) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js index b75784c95c520..2ba6e38081e6e 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.js @@ -13,6 +13,8 @@ import { parseInterval } from '../../../../common/util/parse_interval'; import { getEntityFieldList } from '../../../../common/util/anomaly_utils'; import { buildConfigFromDetector } from '../../util/chart_config_builder'; import { mlJobService } from '../../services/job_service'; +import { mlFunctionToESAggregation } from '../../../../common/util/job_utils'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; // Builds the chart configuration for the provided anomaly record, returning // an object with properties used for the display (series function and field, aggregation interval etc), @@ -48,6 +50,10 @@ export function buildConfig(record) { // define the metric series to be plotted. config.entityFields = getEntityFieldList(record); + if (record.function === ML_JOB_AGGREGATION.METRIC) { + config.metricFunction = mlFunctionToESAggregation(record.function_description); + } + // Build the tooltip data for the chart info icon, showing further details on what is being plotted. let functionLabel = config.metricFunction; if (config.metricFieldName !== undefined) { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index b9634f0eac359..39166841a4e1b 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -46,8 +46,6 @@ const ML_TIME_FIELD_NAME = 'timestamp'; const USE_OVERALL_CHART_LIMITS = false; const MAX_CHARTS_PER_ROW = 4; -// callback(getDefaultChartsData()); - export const anomalyDataChange = function ( chartsContainerWidth, anomalyRecords, diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index e4cf43ac91727..9331fdc04b7bb 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -38,6 +38,7 @@ import { useResolver } from '../use_resolver'; import { basicResolvers } from '../resolvers'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { useTimefilter } from '../../contexts/kibana'; +import { useToastNotificationService } from '../../services/toast_notification_service'; export const timeSeriesExplorerRouteFactory = ( navigateToPath: NavigateToPath, @@ -88,6 +89,7 @@ export const TimeSeriesExplorerUrlStateManager: FC { + const toastNotificationService = useToastNotificationService(); const [appState, setAppState] = useUrlState('_a'); const [globalState, setGlobalState] = useUrlState('_g'); const [lastRefresh, setLastRefresh] = useState(0); @@ -293,6 +295,7 @@ export const TimeSeriesExplorerUrlStateManager: FC ({ latestMs: number, dateFormatTz: string, maxRecords: number, - maxExamples: number, - influencersFilterQuery: any + maxExamples?: number, + influencersFilterQuery?: any, + functionDescription?: string ) { const body = JSON.stringify({ jobIds, @@ -39,6 +40,7 @@ export const resultsApiProvider = (httpService: HttpService) => ({ maxRecords, maxExamples, influencersFilterQuery, + functionDescription, }); return httpService.http$({ diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index 7c2c28fe9385c..2869a7439614f 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -19,6 +19,7 @@ import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; import { JobId } from '../../../../common/types/anomaly_detection_jobs'; import { MlApiServices } from '../ml_api_service'; import { CriteriaField } from './index'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; interface ResultResponse { success: boolean; @@ -347,9 +348,10 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { jobIds: string[], criteriaFields: CriteriaField[], threshold: any, - earliestMs: number, - latestMs: number, - maxResults: number | undefined + earliestMs: number | null, + latestMs: number | null, + maxResults: number | undefined, + functionDescription?: string ): Observable { const obj: RecordsForCriteria = { success: true, records: [] }; @@ -400,6 +402,19 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { }); }); + if (functionDescription !== undefined) { + const mlFunctionToPlotIfMetric = + functionDescription !== undefined + ? aggregationTypeTransform.toML(functionDescription) + : functionDescription; + + boolCriteria.push({ + term: { + function_description: mlFunctionToPlotIfMetric, + }, + }); + } + return mlApiServices.results .anomalySearch$( { diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index aae0cb51aa81d..962f384cf5b1b 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -76,6 +76,7 @@ export function resultsServiceProvider( criteriaFields: any[], earliestMs: number, latestMs: number, - intervalMs: number + intervalMs: number, + actualPlotFunctionIfMetric?: string ): Promise; }; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index 14a725c2e22b7..d053d69b4d1f2 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -12,6 +12,7 @@ import { ANOMALY_SWIM_LANE_HARD_LIMIT, SWIM_LANE_DEFAULT_PAGE_SIZE, } from '../../explorer/explorer_constants'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; /** * Service for carrying out Elasticsearch queries to obtain data for the Ml Results dashboards. @@ -1293,7 +1294,14 @@ export function resultsServiceProvider(mlApiServices) { // criteria, time range, and aggregation interval. // criteriaFields parameter must be an array, with each object in the array having 'fieldName' // 'fieldValue' properties. - getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, latestMs, intervalMs) { + getRecordMaxScoreByTime( + jobId, + criteriaFields, + earliestMs, + latestMs, + intervalMs, + actualPlotFunctionIfMetric + ) { return new Promise((resolve, reject) => { const obj = { success: true, @@ -1321,7 +1329,18 @@ export function resultsServiceProvider(mlApiServices) { }, }); }); + if (actualPlotFunctionIfMetric !== undefined) { + const mlFunctionToPlotIfMetric = + actualPlotFunctionIfMetric !== undefined + ? aggregationTypeTransform.toML(actualPlotFunctionIfMetric) + : actualPlotFunctionIfMetric; + mustCriteria.push({ + term: { + function_description: mlFunctionToPlotIfMetric, + }, + }); + } mlApiServices.results .anomalySearch( { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts new file mode 100644 index 0000000000000..b8247eb91e1f5 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { PlotByFunctionControls } from './plot_function_controls'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx new file mode 100644 index 0000000000000..0356c20fecb9a --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const plotByFunctionOptions = [ + { + value: 'mean', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByAvgOptionLabel', { + defaultMessage: 'mean', + }), + }, + { + value: 'min', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMinOptionLabel', { + defaultMessage: 'min', + }), + }, + { + value: 'max', + text: i18n.translate('xpack.ml.timeSeriesExplorer.plotByMaxOptionLabel', { + defaultMessage: 'max', + }), + }, +]; +export const PlotByFunctionControls = ({ + functionDescription, + setFunctionDescription, +}: { + functionDescription: undefined | string; + setFunctionDescription: (func: string) => void; +}) => { + if (functionDescription === undefined) return null; + return ( + + + setFunctionDescription(e.target.value)} + aria-label={i18n.translate('xpack.ml.timeSeriesExplorer.metricPlotByOptionLabel', { + defaultMessage: 'Pick function to plot by (min, max, or average) if metric function', + })} + /> + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 3169ecfd1bbc7..8df186c5c3c6e 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -1475,6 +1475,22 @@ class TimeseriesChartIntl extends Component { }); } + if (marker.metricFunction) { + tooltipData.push({ + label: i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.metricActualPlotFunctionLabel', + { + defaultMessage: 'function', + } + ), + value: marker.metricFunction, + seriesIdentifier: { + key: seriesKey, + }, + valueAccessor: 'metric_function', + }); + } + if (modelPlotEnabled === false) { // Show actual/typical when available except for rare detectors. // Rare detectors always have 1 as actual and the probability as typical. diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts new file mode 100644 index 0000000000000..f9775976206c2 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_criteria_fields.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Updates criteria fields for API calls, e.g. getAnomaliesTableData + * @param detectorIndex + * @param entities + */ +export const getCriteriaFields = (detectorIndex: number, entities: Record) => { + // Only filter on the entity if the field has a value. + const nonBlankEntities = entities.filter( + (entity: { fieldValue: any }) => entity.fieldValue !== null + ); + return [ + { + fieldName: 'detector_index', + fieldValue: detectorIndex, + }, + ...nonBlankEntities, + ]; +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts new file mode 100644 index 0000000000000..029e4645cfe26 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/get_function_description.ts @@ -0,0 +1,62 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { mlResultsService } from '../services/results_service'; +import { ToastNotificationService } from '../services/toast_notification_service'; +import { getControlsForDetector } from './get_controls_for_detector'; +import { getCriteriaFields } from './get_criteria_fields'; +import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; +import { ML_JOB_AGGREGATION } from '../../../common/constants/aggregation_types'; + +/** + * Get the function description from the record with the highest anomaly score + */ +export const getFunctionDescription = async ( + { + selectedDetectorIndex, + selectedEntities, + selectedJobId, + selectedJob, + }: { + selectedDetectorIndex: number; + selectedEntities: Record; + selectedJobId: string; + selectedJob: CombinedJob; + }, + toastNotificationService: ToastNotificationService +) => { + // if the detector's function is metric, fetch the highest scoring anomaly record + // and set to plot the function_description (avg/min/max) of that record by default + if ( + selectedJob?.analysis_config?.detectors[selectedDetectorIndex]?.function !== + ML_JOB_AGGREGATION.METRIC + ) + return; + + const entityControls = getControlsForDetector( + selectedDetectorIndex, + selectedEntities, + selectedJobId + ); + const criteriaFields = getCriteriaFields(selectedDetectorIndex, entityControls); + try { + const resp = await mlResultsService + .getRecordsForCriteria([selectedJob.job_id], criteriaFields, 0, null, null, 1) + .toPromise(); + if (Array.isArray(resp?.records) && resp.records.length === 1) { + const highestScoringAnomaly = resp.records[0]; + return highestScoringAnomaly?.function_description; + } + } catch (error) { + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.timeSeriesExplorer.highestAnomalyScoreErrorToastTitle', { + defaultMessage: 'An error occurred getting record with the highest anomaly score', + }) + ); + } +}; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index e43ba8c87083a..0d7abdab90be0 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -26,7 +26,8 @@ function getMetricData( entityFields: EntityField[], earliestMs: number, latestMs: number, - intervalMs: number + intervalMs: number, + esMetricFunction?: string ): Observable { if ( isModelPlotChartableForDetector(job, detectorIndex) && @@ -88,7 +89,7 @@ function getMetricData( chartConfig.datafeedConfig.indices, entityFields, chartConfig.datafeedConfig.query, - chartConfig.metricFunction, + esMetricFunction ?? chartConfig.metricFunction, chartConfig.metricFieldName, chartConfig.timeField, earliestMs, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 720c1377d4035..e3b6e38f47bab 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -82,6 +82,9 @@ import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/ import { getControlsForDetector } from './get_controls_for_detector'; import { SeriesControls } from './components/series_controls'; import { TimeSeriesChartWithTooltips } from './components/timeseries_chart/timeseries_chart_with_tooltip'; +import { PlotByFunctionControls } from './components/plot_function_controls'; +import { aggregationTypeTransform } from '../../../common/util/anomaly_utils'; +import { getFunctionDescription } from './get_function_description'; // Used to indicate the chart is being plotted across // all partition field values, where the cardinality of the field cannot be @@ -140,6 +143,8 @@ function getTimeseriesexplorerDefaultState() { zoomTo: undefined, zoomFromFocusLoaded: undefined, zoomToFocusLoaded: undefined, + // Sets function to plot by if original function is metric + functionDescription: undefined, }; } @@ -217,6 +222,12 @@ export class TimeSeriesExplorer extends React.Component { }); }; + setFunctionDescription = (selectedFuction) => { + this.setState({ + functionDescription: selectedFuction, + }); + }; + previousChartProps = {}; previousShowAnnotations = undefined; previousShowForecast = undefined; @@ -270,7 +281,7 @@ export class TimeSeriesExplorer extends React.Component { */ getFocusData(selection) { const { selectedJobId, selectedForecastId, selectedDetectorIndex } = this.props; - const { modelPlotEnabled } = this.state; + const { modelPlotEnabled, functionDescription } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); @@ -292,6 +303,7 @@ export class TimeSeriesExplorer extends React.Component { entityControls.filter((entity) => entity.fieldValue !== null), searchBounds, selectedJob, + functionDescription, TIME_FIELD_NAME ); } @@ -322,6 +334,7 @@ export class TimeSeriesExplorer extends React.Component { tableInterval, tableSeverity, } = this.props; + const { functionDescription } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); @@ -335,7 +348,10 @@ export class TimeSeriesExplorer extends React.Component { earliestMs, latestMs, dateFormatTz, - ANOMALIES_TABLE_DEFAULT_QUERY_SIZE + ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, + undefined, + undefined, + functionDescription ) .pipe( map((resp) => { @@ -378,6 +394,24 @@ export class TimeSeriesExplorer extends React.Component { ); }; + getFunctionDescription = async () => { + const { selectedDetectorIndex, selectedEntities, selectedJobId } = this.props; + const selectedJob = mlJobService.getJob(selectedJobId); + + const functionDescriptionToPlot = await getFunctionDescription( + { + selectedDetectorIndex, + selectedEntities, + selectedJobId, + selectedJob, + }, + this.props.toastNotificationService + ); + if (!this.unmounted) { + this.setFunctionDescription(functionDescriptionToPlot); + } + }; + setForecastId = (forecastId) => { this.props.appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); }; @@ -392,13 +426,13 @@ export class TimeSeriesExplorer extends React.Component { zoom, } = this.props; - const { loadCounter: currentLoadCounter } = this.state; + const { loadCounter: currentLoadCounter, functionDescription } = this.state; const currentSelectedJob = mlJobService.getJob(selectedJobId); - if (currentSelectedJob === undefined) { return; } + const functionToPlotByIfMetric = aggregationTypeTransform.toES(functionDescription); this.contextChartSelectedInitCallDone = false; @@ -533,7 +567,8 @@ export class TimeSeriesExplorer extends React.Component { nonBlankEntities, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - stateUpdate.contextAggregationInterval.asMilliseconds() + stateUpdate.contextAggregationInterval.asMilliseconds(), + functionToPlotByIfMetric ) .toPromise() .then((resp) => { @@ -556,7 +591,8 @@ export class TimeSeriesExplorer extends React.Component { this.getCriteriaFields(detectorIndex, entityControls), searchBounds.min.valueOf(), searchBounds.max.valueOf(), - stateUpdate.contextAggregationInterval.asMilliseconds() + stateUpdate.contextAggregationInterval.asMilliseconds(), + functionToPlotByIfMetric ) .then((resp) => { const fullRangeRecordScoreData = processRecordScoreResults(resp.results); @@ -687,7 +723,6 @@ export class TimeSeriesExplorer extends React.Component { if (detectorId !== selectedDetectorIndex) { appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId); } - // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. mlFieldFormatService.populateFormats([jobId]).catch((err) => { console.log('Error populating field formats:', err); @@ -810,7 +845,7 @@ export class TimeSeriesExplorer extends React.Component { this.componentDidUpdate(); } - componentDidUpdate(previousProps) { + componentDidUpdate(previousProps, previousState) { if (previousProps === undefined || previousProps.selectedJobId !== this.props.selectedJobId) { this.contextChartSelectedInitCallDone = false; this.setState({ fullRefresh: false, loading: true }, () => { @@ -818,6 +853,15 @@ export class TimeSeriesExplorer extends React.Component { }); } + if ( + previousProps === undefined || + previousProps.selectedJobId !== this.props.selectedJobId || + previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities) + ) { + this.getFunctionDescription(); + } + if ( previousProps === undefined || previousProps.selectedForecastId !== this.props.selectedForecastId @@ -840,7 +884,8 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || - previousProps.selectedJobId !== this.props.selectedJobId + previousProps.selectedJobId !== this.props.selectedJobId || + previousState.functionDescription !== this.state.functionDescription ) { const fullRefresh = previousProps === undefined || @@ -848,7 +893,8 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || - previousProps.selectedJobId !== this.props.selectedJobId; + previousProps.selectedJobId !== this.props.selectedJobId || + previousState.functionDescription !== this.state.functionDescription; this.loadSingleMetricData(fullRefresh); } @@ -919,8 +965,8 @@ export class TimeSeriesExplorer extends React.Component { zoomTo, zoomFromFocusLoaded, zoomToFocusLoaded, + functionDescription, } = this.state; - const chartProps = { modelPlotEnabled, contextChartData, @@ -939,7 +985,6 @@ export class TimeSeriesExplorer extends React.Component { zoomToFocusLoaded, autoZoomDuration, }; - const jobs = createTimeSeriesJobData(mlJobService.jobs); if (selectedDetectorIndex === undefined || mlJobService.getJob(selectedJobId) === undefined) { @@ -992,7 +1037,6 @@ export class TimeSeriesExplorer extends React.Component { )} - + {functionDescription && ( + + )} + {arePartitioningFieldsProvided && ( @@ -1014,7 +1068,6 @@ export class TimeSeriesExplorer extends React.Component { )} - {fullRefresh && loading === true && ( diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts index d1576be18d5bf..044e5dfd6fe13 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts @@ -26,6 +26,7 @@ import { mlForecastService } from '../../services/forecast_service'; import { mlFunctionToESAggregation } from '../../../../common/util/job_utils'; import { GetAnnotationsResponse } from '../../../../common/types/annotations'; import { ANNOTATION_EVENT_USER } from '../../../../common/constants/annotations'; +import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; export interface Interval { asMilliseconds: () => number; @@ -51,8 +52,14 @@ export function getFocusData( modelPlotEnabled: boolean, nonBlankEntities: any[], searchBounds: any, - selectedJob: Job + selectedJob: Job, + functionDescription?: string | undefined ): Observable { + const esFunctionToPlotIfMetric = + functionDescription !== undefined + ? aggregationTypeTransform.toES(functionDescription) + : functionDescription; + return forkJoin([ // Query 1 - load metric data across selected time range. mlTimeSeriesSearchService.getMetricData( @@ -61,7 +68,8 @@ export function getFocusData( nonBlankEntities, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - focusAggregationInterval.asMilliseconds() + focusAggregationInterval.asMilliseconds(), + esFunctionToPlotIfMetric ), // Query 2 - load all the records across selected time range for the chart anomaly markers. mlResultsService.getRecordsForCriteria( @@ -70,7 +78,8 @@ export function getFocusData( 0, searchBounds.min.valueOf(), searchBounds.max.valueOf(), - ANOMALIES_TABLE_DEFAULT_QUERY_SIZE + ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, + functionDescription ), // Query 3 - load any scheduled events for the selected job. mlResultsService.getScheduledEventsByBucket( @@ -143,7 +152,8 @@ export function getFocusData( focusChartData, anomalyRecords, focusAggregationInterval, - modelPlotEnabled + modelPlotEnabled, + functionDescription ); focusChartData = processScheduledEventsForChart(focusChartData, scheduledEvents); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts index 1b7a740d90dde..4b101f888e4ea 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts @@ -16,7 +16,8 @@ export function processDataForFocusAnomalies( chartData: any, anomalyRecords: any, aggregationInterval: any, - modelPlotEnabled: any + modelPlotEnabled: any, + functionDescription: any ): any; export function processScheduledEventsForChart(chartData: any, scheduledEvents: any): any; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index d24794382128d..5dc3a454e41e7 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -19,6 +19,7 @@ import { parseInterval } from '../../../../common/util/parse_interval'; import { getBoundsRoundedToInterval, getTimeBucketsFromCache } from '../../util/time_buckets'; import { CHARTS_POINT_TARGET, TIME_FIELD_NAME } from '../timeseriesexplorer_constants'; +import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; // create new job objects based on standard job config objects // new job objects just contain job id, bucket span in seconds and a selected flag. @@ -100,7 +101,8 @@ export function processDataForFocusAnomalies( chartData, anomalyRecords, aggregationInterval, - modelPlotEnabled + modelPlotEnabled, + functionDescription ) { const timesToAddPointsFor = []; @@ -142,6 +144,12 @@ export function processDataForFocusAnomalies( // Look for a chart point with the same time as the record. // If none found, find closest time in chartData set. const recordTime = record[TIME_FIELD_NAME]; + if ( + record.function === ML_JOB_AGGREGATION.METRIC && + record.function_description !== functionDescription + ) + return; + const chartPoint = findChartPointForAnomalyTime(chartData, recordTime, aggregationInterval); if (chartPoint !== undefined) { // If chart aggregation interval > bucket span, there may be more than @@ -160,6 +168,10 @@ export function processDataForFocusAnomalies( chartPoint.value = record.actual; } + if (record.function === ML_JOB_AGGREGATION.METRIC) { + chartPoint.value = Array.isArray(record.actual) ? record.actual[0] : record.actual; + } + chartPoint.actual = record.actual; chartPoint.typical = record.typical; } else { diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 53a35f6310978..a196f1034fdd3 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -54,7 +54,8 @@ export function resultsServiceProvider(mlClient: MlClient) { dateFormatTz: string, maxRecords: number = ANOMALIES_TABLE_DEFAULT_QUERY_SIZE, maxExamples: number = DEFAULT_MAX_EXAMPLES, - influencersFilterQuery: any + influencersFilterQuery?: any, + functionDescription?: string ) { // Build the query to return the matching anomaly record results. // Add criteria for the time range, record score, plus any specified job IDs. @@ -102,6 +103,13 @@ export function resultsServiceProvider(mlClient: MlClient) { }, }); }); + if (functionDescription !== undefined) { + boolCriteria.push({ + term: { + function_description: functionDescription, + }, + }); + } if (influencersFilterQuery !== undefined) { boolCriteria.push(influencersFilterQuery); diff --git a/x-pack/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts index ce892b227c04e..e708dd71043d0 100644 --- a/x-pack/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -36,6 +36,7 @@ function getAnomaliesTableData(mlClient: MlClient, payload: any) { maxRecords, maxExamples, influencersFilterQuery, + functionDescription, } = payload; return rs.getAnomaliesTableData( jobIds, @@ -48,7 +49,8 @@ function getAnomaliesTableData(mlClient: MlClient, payload: any) { dateFormatTz, maxRecords, maxExamples, - influencersFilterQuery + influencersFilterQuery, + functionDescription ); } diff --git a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts index 5cd0ecdfbec90..30a9054c69238 100644 --- a/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts @@ -26,6 +26,7 @@ export const anomaliesTableDataSchema = schema.object({ maxRecords: schema.number(), maxExamples: schema.maybe(schema.number()), influencersFilterQuery: schema.maybe(schema.any()), + functionDescription: schema.maybe(schema.nullable(schema.string())), }); export const categoryDefinitionSchema = schema.object({ From 1885dda6e6b567a81433fe574dd68d3057725da1 Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 9 Nov 2020 11:01:33 -0600 Subject: [PATCH 25/87] Fix test import objects (#82767) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/management/_import_objects.ts | 19 +++++++++++-------- x-pack/test/functional/config.js | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index 4a7a85c738fc2..52428c944d666 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -37,7 +37,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { - // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); await esArchiver.load('management'); @@ -471,16 +470,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should display an explicit error message when importing a file bigger than allowed', async () => { - await PageObjects.savedObjects.importFile( - path.join(__dirname, 'exports', '_import_too_big.ndjson') - ); + describe('when bigger than savedObjects.maxImportPayloadBytes (not Cloud)', function () { + // see --savedObjects.maxImportPayloadBytes in config file + this.tags(['skipCloud']); + it('should display an explicit error message when importing a file bigger than allowed', async () => { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_too_big.ndjson') + ); - await PageObjects.savedObjects.checkImportError(); + await PageObjects.savedObjects.checkImportError(); - const errorText = await PageObjects.savedObjects.getImportErrorText(); + const errorText = await PageObjects.savedObjects.getImportErrorText(); - expect(errorText).to.contain(`Payload content length greater than maximum allowed`); + expect(errorText).to.contain(`Payload content length greater than maximum allowed`); + }); }); it('should display an explicit error message when importing an invalid file', async () => { diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 2072f4aa1c571..11e4111696ccf 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -88,6 +88,7 @@ export default async function ({ readConfigFile }) { '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--timelion.ui.enabled=true', + '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects ], }, uiSettings: { From e1b7073a643def8ff3bb7aa9254067957cbdf60f Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 9 Nov 2020 10:08:00 -0700 Subject: [PATCH 26/87] [Alerting][Connectors] Add new executor subaction to get 3rd party case fields (#82519) --- x-pack/plugins/actions/README.md | 21 +- .../builtin_action_types/jira/api.test.ts | 1478 ++++++++--------- .../server/builtin_action_types/jira/api.ts | 7 + .../server/builtin_action_types/jira/index.ts | 8 + .../server/builtin_action_types/jira/mocks.ts | 15 +- .../builtin_action_types/jira/schema.ts | 5 + .../builtin_action_types/jira/service.test.ts | 171 +- .../builtin_action_types/jira/service.ts | 55 +- .../server/builtin_action_types/jira/types.ts | 48 +- .../builtin_action_types/resilient/api.ts | 10 +- .../builtin_action_types/resilient/index.ts | 11 +- .../builtin_action_types/resilient/mocks.ts | 267 +++ .../builtin_action_types/resilient/schema.ts | 5 + .../resilient/service.test.ts | 45 +- .../builtin_action_types/resilient/service.ts | 21 +- .../builtin_action_types/resilient/types.ts | 35 +- .../servicenow/api.test.ts | 1215 +++++++------- .../builtin_action_types/servicenow/api.ts | 12 +- .../builtin_action_types/servicenow/index.ts | 18 +- .../builtin_action_types/servicenow/mocks.ts | 28 + .../builtin_action_types/servicenow/schema.ts | 6 + .../servicenow/service.test.ts | 39 +- .../servicenow/service.ts | 24 +- .../builtin_action_types/servicenow/types.ts | 30 +- .../actions/builtin_action_types/jira.ts | 10 +- .../actions/builtin_action_types/resilient.ts | 10 +- .../builtin_action_types/servicenow.ts | 10 +- 27 files changed, 2156 insertions(+), 1448 deletions(-) diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 4fef9bc582d08..432a4bfff7a6b 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -69,18 +69,21 @@ Table of Contents - [`secrets`](#secrets-6) - [`params`](#params-6) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice) + - [`subActionParams (getFields)`](#subactionparams-getfields-1) - [Jira](#jira) - [`config`](#config-7) - [`secrets`](#secrets-7) - [`params`](#params-7) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1) - [`subActionParams (issueTypes)`](#subactionparams-issuetypes) + - [`subActionParams (getFields)`](#subactionparams-getfields-2) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2) - [IBM Resilient](#ibm-resilient) - [`config`](#config-8) - [`secrets`](#secrets-8) - [`params`](#params-8) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3) + - [`subActionParams (getFields)`](#subactionparams-getfields-3) - [Command Line Utility](#command-line-utility) - [Developing New Action Types](#developing-new-action-types) - [licensing](#licensing) @@ -563,7 +566,7 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a | Property | Description | Type | | --------------- | ------------------------------------------------------------------------------------ | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -580,6 +583,10 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a | urgency | The name of the urgency in ServiceNow. | string _(optional)_ | | impact | The name of the impact in ServiceNow. | string _(optional)_ | +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + --- ## Jira @@ -606,7 +613,7 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla | Property | Description | Type | | --------------- | ----------------------------------------------------------------------------------------------------------------------- | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -627,6 +634,10 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla No parameters for `issueTypes` sub-action. Provide an empty object `{}`. +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + #### `subActionParams (pushToService)` | Property | Description | Type | @@ -655,7 +666,7 @@ ID: `.resilient` | Property | Description | Type | | --------------- | ------------------------------------------------------------------------------------ | ------ | -| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string | +| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string | | subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -670,6 +681,10 @@ ID: `.resilient` | incidentTypes | An array with the ids of IBM Resilient incident types. | number[] _(optional)_ | | severityCode | IBM Resilient id of the severity code. | number _(optional)_ | +#### `subActionParams (getFields)` + +No parameters for `getFields` sub-action. Provide an empty object `{}`. + # Command Line Utility The [`kbn-action`](https://github.com/pmuellr/kbn-action) tool can be used to send HTTP requests to the Actions plugin. For instance, to create a Slack action from the `.slack` Action Type, use the following command: diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts index e8fa9f76df778..5a7617ada1bf0 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts @@ -15,804 +15,792 @@ describe('api', () => { beforeEach(() => { externalService = externalServiceMock.create(); - jest.clearAllMocks(); }); - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('pushToService', () => { - describe('create incident - cases', () => { - test('it creates an incident', async () => { - const params = { ...apiParams, externalId: null }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - ], - }); - }); - - test('it creates an incident without comments', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - }); - }); - - test('it calls createIncident correctly', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)', - summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)', - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); - }); - - test('it calls createIncident correctly without mapping', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - description: 'Incident description', - summary: 'Incident title', - issueType: '10006', - labels: ['kibana', 'elastic'], - priority: 'High', - parent: null, - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); + describe('create incident - cases', () => { + test('it creates an incident', async () => { + const params = { ...apiParams, externalId: null }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, }); - test('it calls createComment correctly', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', + comments: [ + { commentId: 'case-comment-1', - comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + pushedDate: '2020-04-27T10:59:46.202Z', }, - }); - - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { + { commentId: 'case-comment-2', - comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + pushedDate: '2020-04-27T10:59:46.202Z', }, - }); + ], }); + }); - test('it calls createComment correctly without mapping', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - }, - }); + test('it creates an incident without comments', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, + }); - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - }, - }); + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', }); }); - describe('update incident', () => { - test('it updates an incident', async () => { - const res = await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-04-27T10:59:46.202Z', - }, - ], - }); - }); - - test('it updates an incident without comments', async () => { - const params = { ...apiParams, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'CK-1', - pushedDate: '2020-04-27T10:59:46.202Z', - url: 'https://siem-kibana.atlassian.net/browse/CK-1', - }); - }); - - test('it calls updateIncident correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + test('it calls createIncident correctly', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)', + summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls createIncident correctly without mapping', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + description: 'Incident description', + summary: 'Incident title', + issueType: '10006', + labels: ['kibana', 'elastic'], + priority: 'High', + parent: null, + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls createComment correctly', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident correctly without mapping', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - description: 'Incident description', - summary: 'Incident title', - issueType: '10006', - labels: ['kibana', 'elastic'], - priority: 'High', - parent: null, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); + }, }); - test('it calls createComment correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, }); + }); - test('it calls createComment correctly without mapping', async () => { - const params = { ...apiParams }; - await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); - expect(externalService.createComment).toHaveBeenCalledTimes(2); - expect(externalService.createComment).toHaveBeenNthCalledWith(1, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-1', - comment: 'A comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + test('it calls createComment correctly without mapping', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', + }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, + }); - expect(externalService.createComment).toHaveBeenNthCalledWith(2, { - incidentId: 'incident-1', - comment: { - commentId: 'case-comment-2', - comment: 'Another comment', - createdAt: '2020-04-27T10:59:46.202Z', - createdBy: { - fullName: 'Elastic User', - username: 'elastic', - }, - updatedAt: '2020-04-27T10:59:46.202Z', - updatedBy: { - fullName: 'Elastic User', - username: 'elastic', - }, + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', + }, + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); + }, }); }); + }); - describe('issueTypes', () => { - test('it returns the issue types correctly', async () => { - const res = await api.issueTypes({ - externalService, - params: {}, - }); - expect(res).toEqual([ + describe('update incident', () => { + test('it updates an incident', async () => { + const res = await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', + comments: [ { - id: '10006', - name: 'Task', + commentId: 'case-comment-1', + pushedDate: '2020-04-27T10:59:46.202Z', }, { - id: '10007', - name: 'Bug', + commentId: 'case-comment-2', + pushedDate: '2020-04-27T10:59:46.202Z', }, - ]); + ], }); }); - describe('fieldsByIssueType', () => { - test('it returns the fields correctly', async () => { - const res = await api.fieldsByIssueType({ - externalService, - params: { id: '10006' }, - }); - expect(res).toEqual({ - summary: { allowedValues: [], defaultValue: {} }, - priority: { - allowedValues: [ - { - name: 'Medium', - id: '3', - }, - ], - defaultValue: { name: 'Medium', id: '3' }, - }, - }); + test('it updates an incident without comments', async () => { + const params = { ...apiParams, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'CK-1', + pushedDate: '2020-04-27T10:59:46.202Z', + url: 'https://siem-kibana.atlassian.net/browse/CK-1', }); }); - describe('getIssues', () => { - test('it returns the issues correctly', async () => { - const res = await api.issues({ - externalService, - params: { title: 'Title test' }, - }); - expect(res).toEqual([ - { - id: '10267', - key: 'RJ-107', - title: 'Test title', - }, - ]); + test('it calls updateIncident correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, }); + expect(externalService.createIncident).not.toHaveBeenCalled(); }); - describe('getIssue', () => { - test('it returns the issue correctly', async () => { - const res = await api.issue({ - externalService, - params: { id: 'RJ-107' }, - }); - expect(res).toEqual({ - id: '10267', - key: 'RJ-107', - title: 'Test title', - }); + test('it calls updateIncident correctly without mapping', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + description: 'Incident description', + summary: 'Incident title', + issueType: '10006', + labels: ['kibana', 'elastic'], + priority: 'High', + parent: null, + }, }); + expect(externalService.createIncident).not.toHaveBeenCalled(); }); - describe('mapping variations', () => { - test('overwrite & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - }, - }); - }); - - test('nothing & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + test('it calls createComment correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & append', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('nothing & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, + }, + }); + + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('overwrite & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('overwrite & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('it calls createComment correctly without mapping', async () => { + const params = { ...apiParams }; + await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger }); + expect(externalService.createComment).toHaveBeenCalledTimes(2); + expect(externalService.createComment).toHaveBeenNthCalledWith(1, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-1', + comment: 'A comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('nothing & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & overwrite', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', - description: - 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + + expect(externalService.createComment).toHaveBeenNthCalledWith(2, { + incidentId: 'incident-1', + comment: { + commentId: 'case-comment-2', + comment: 'Another comment', + createdAt: '2020-04-27T10:59:46.202Z', + createdBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('append & nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - labels: ['kibana', 'elastic'], - priority: 'High', - issueType: '10006', - parent: null, - summary: - 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + updatedAt: '2020-04-27T10:59:46.202Z', + updatedBy: { + fullName: 'Elastic User', + username: 'elastic', }, - }); - }); - - test('comment nothing', async () => { - mapping.set('title', { - target: 'summary', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'nothing', - }); - - mapping.set('summary', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - logger: mockedLogger, - }); - expect(externalService.createComment).not.toHaveBeenCalled(); + }, + }); + }); + }); + + describe('issueTypes', () => { + test('it returns the issue types correctly', async () => { + const res = await api.issueTypes({ + externalService, + params: {}, + }); + expect(res).toEqual([ + { + id: '10006', + name: 'Task', + }, + { + id: '10007', + name: 'Bug', + }, + ]); + }); + }); + + describe('fieldsByIssueType', () => { + test('it returns the fields correctly', async () => { + const res = await api.fieldsByIssueType({ + externalService, + params: { id: '10006' }, + }); + expect(res).toEqual({ + summary: { allowedValues: [], defaultValue: {} }, + priority: { + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + ], + defaultValue: { name: 'Medium', id: '3' }, + }, + }); + }); + }); + + describe('getIssues', () => { + test('it returns the issues correctly', async () => { + const res = await api.issues({ + externalService, + params: { title: 'Title test' }, + }); + expect(res).toEqual([ + { + id: '10267', + key: 'RJ-107', + title: 'Test title', + }, + ]); + }); + }); + + describe('getIssue', () => { + test('it returns the issue correctly', async () => { + const res = await api.issue({ + externalService, + params: { id: 'RJ-107' }, + }); + expect(res).toEqual({ + id: '10267', + key: 'RJ-107', + title: 'Test title', + }); + }); + }); + + describe('mapping variations', () => { + test('overwrite & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & append', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: + 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + }, + }); + }); + + test('overwrite & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('overwrite & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('nothing & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & overwrite', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('append & nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + labels: ['kibana', 'elastic'], + priority: 'High', + issueType: '10006', + parent: null, + summary: + 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)', + }, + }); + }); + + test('comment nothing', async () => { + mapping.set('title', { + target: 'summary', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'nothing', + }); + + mapping.set('summary', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + logger: mockedLogger, }); + expect(externalService.createComment).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts index 679c1541964ce..feeb69b1d1a0e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts @@ -17,6 +17,7 @@ import { PushToServiceApiParams, PushToServiceResponse, GetIssueHandlerArgs, + GetCommonFieldsHandlerArgs, } from './types'; // TODO: to remove, need to support Case @@ -39,6 +40,11 @@ const getIssueTypesHandler = async ({ externalService }: GetIssueTypesHandlerArg return res; }; +const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => { + const res = await externalService.getFields(); + return res; +}; + const getFieldsByIssueTypeHandler = async ({ externalService, params, @@ -157,6 +163,7 @@ const pushToServiceHandler = async ({ }; export const api: ExternalServiceApi = { + getFields: getFieldsHandler, handshake: handshakeHandler, pushToService: pushToServiceHandler, getIncident: getIncidentHandler, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts index 9d6ff90c33700..c70c0810926f4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts @@ -40,6 +40,7 @@ interface GetActionTypeParams { } const supportedSubActions: string[] = [ + 'getFields', 'pushToService', 'issueTypes', 'fieldsByIssueType', @@ -145,6 +146,13 @@ async function executor( }); } + if (subAction === 'getFields') { + data = await api.getFields({ + externalService, + params: subActionParams, + }); + } + if (subAction === 'issues') { const getIssuesParams = subActionParams as ExecutorSubActionGetIssuesParams; data = await api.issues({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts index b98eda799e3aa..87a0f156a0c2a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts @@ -73,6 +73,20 @@ const createMock = (): jest.Mocked => { key: 'RJ-107', title: 'Test title', })), + getFields: jest.fn().mockImplementation(() => ({ + description: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + summary: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + })), }; service.createComment.mockImplementationOnce(() => @@ -97,7 +111,6 @@ const createMock = (): jest.Mocked => { const externalServiceMock = { create: createMock, }; - const mapping: Map> = new Map(); mapping.set('title', { diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts index 513ca2cf18e6c..70b60ada9c386 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts @@ -55,6 +55,7 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ }); // Reserved for future implementation +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionGetCapabilitiesParamsSchema = schema.object({}); export const ExecutorSubActionGetIssueTypesParamsSchema = schema.object({}); @@ -65,6 +66,10 @@ export const ExecutorSubActionGetIssuesParamsSchema = schema.object({ title: sch export const ExecutorSubActionGetIssueParamsSchema = schema.object({ id: schema.string() }); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts index fe4e135c76fc3..2165ba56428c9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts @@ -57,8 +57,10 @@ const fieldsResponse = { id: '10006', name: 'Task', fields: { - summary: { fieldId: 'summary' }, + summary: { required: true, schema: { type: 'string' }, fieldId: 'summary' }, priority: { + required: false, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -198,7 +200,7 @@ describe('Jira service', () => { error.response = { data: { errors: { summary: 'Required field' } } }; throw error; }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( '[Action][Jira]: Unable to get incident with id 1. Error: An error has occurred Reason: Required field' ); }); @@ -348,7 +350,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.createIncident({ incident: { summary: 'title', @@ -442,7 +444,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { @@ -526,7 +528,7 @@ describe('Jira service', () => { throw error; }); - expect( + await expect( service.createComment({ incidentId: '1', comment: { @@ -587,7 +589,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getCapabilities()).rejects.toThrow( + await expect(service.getCapabilities()).rejects.toThrow( '[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Could not get capabilities' ); }); @@ -657,7 +659,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssueTypes()).rejects.toThrow( + await expect(service.getIssueTypes()).rejects.toThrow( '[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -741,7 +743,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssueTypes()).rejects.toThrow( + await expect(service.getIssueTypes()).rejects.toThrow( '[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -765,6 +767,8 @@ describe('Jira service', () => { expect(res).toEqual({ priority: { + required: false, + schema: { type: 'string' }, allowedValues: [ { id: '1', name: 'Highest' }, { id: '2', name: 'High' }, @@ -774,7 +778,12 @@ describe('Jira service', () => { ], defaultValue: { id: '3', name: 'Medium' }, }, - summary: { allowedValues: [], defaultValue: {} }, + summary: { + required: true, + schema: { type: 'string' }, + allowedValues: [], + defaultValue: {}, + }, }); }); @@ -815,7 +824,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getFieldsByIssueType('10006')).rejects.toThrow( + await expect(service.getFieldsByIssueType('10006')).rejects.toThrow( '[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get fields' ); }); @@ -837,8 +846,10 @@ describe('Jira service', () => { requestMock.mockImplementationOnce(() => ({ data: { values: [ - { fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, { + required: false, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -859,10 +870,17 @@ describe('Jira service', () => { expect(res).toEqual({ priority: { + required: false, + schema: { type: 'string' }, allowedValues: [{ id: '3', name: 'Medium' }], defaultValue: { id: '3', name: 'Medium' }, }, - summary: { allowedValues: [], defaultValue: {} }, + summary: { + required: true, + schema: { type: 'string' }, + allowedValues: [], + defaultValue: {}, + }, }); }); @@ -881,8 +899,10 @@ describe('Jira service', () => { requestMock.mockImplementationOnce(() => ({ data: { values: [ - { fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, { + required: true, + schema: { type: 'string' }, fieldId: 'priority', allowedValues: [ { @@ -927,7 +947,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getFieldsByIssueType('10006')).rejects.toThrow( + await expect(service.getFieldsByIssueType('10006')).rejects.toThrowError( '[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -976,7 +996,7 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssues('Test title')).rejects.toThrow( + await expect(service.getIssues('Test title')).rejects.toThrow( '[Action][Jira]: Unable to get issues. Error: An error has occurred. Reason: Could not get issue types' ); }); @@ -1020,9 +1040,128 @@ describe('Jira service', () => { throw error; }); - expect(service.getIssue('RJ-107')).rejects.toThrow( + await expect(service.getIssue('RJ-107')).rejects.toThrow( '[Action][Jira]: Unable to get issue with id RJ-107. Error: An error has occurred. Reason: Could not get issue types' ); }); }); + + describe('getFields', () => { + const callMocks = () => { + requestMock + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: issueTypesResponse.data.projects[0].issuetypes, + }, + })) + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + capabilities: { + 'list-project-issuetypes': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes', + 'list-issuetype-fields': + 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields', + }, + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: [ + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'description' }, + { + required: false, + schema: { type: 'string' }, + fieldId: 'priority', + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + ], + defaultValue: { + name: 'Medium', + id: '3', + }, + }, + ], + }, + })) + .mockImplementationOnce(() => ({ + data: { + values: [ + { required: true, schema: { type: 'string' }, fieldId: 'summary' }, + { required: true, schema: { type: 'string' }, fieldId: 'description' }, + ], + }, + })); + }; + beforeEach(() => { + jest.resetAllMocks(); + }); + test('it should call request with correct arguments', async () => { + callMocks(); + await service.getFields(); + const callUrls = [ + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes', + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/capabilities', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10006', + 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10007', + ]; + requestMock.mock.calls.forEach((call, i) => { + expect(call[0].url).toEqual(callUrls[i]); + }); + }); + test('it returns common fields correctly', async () => { + callMocks(); + const res = await service.getFields(); + expect(res).toEqual({ + description: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + summary: { + allowedValues: [], + defaultValue: {}, + required: true, + schema: { type: 'string' }, + }, + }); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + const error: ResponseError = new Error('An error has occurred'); + error.response = { data: { errors: { summary: 'Required field' } } }; + throw error; + }); + await expect(service.getFields()).rejects.toThrow( + '[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Required field' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts index f5347891f4f70..b3c5bb4a84de5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts @@ -8,18 +8,20 @@ import axios from 'axios'; import { Logger } from '../../../../../../src/core/server'; import { - ExternalServiceCredentials, - ExternalService, + CreateCommentParams, CreateIncidentParams, - UpdateIncidentParams, - JiraPublicConfigurationType, - JiraSecretConfigurationType, + ExternalService, + ExternalServiceCommentResponse, + ExternalServiceCredentials, + ExternalServiceIncidentResponse, Fields, - CreateCommentParams, + FieldSchema, + GetCommonFieldsResponse, Incident, + JiraPublicConfigurationType, + JiraSecretConfigurationType, ResponseError, - ExternalServiceCommentResponse, - ExternalServiceIncidentResponse, + UpdateIncidentParams, } from './types'; import * as i18n from './translations'; @@ -127,14 +129,21 @@ export const createExternalService = ( issueTypes.map((type) => ({ id: type.id, name: type.name })); const normalizeFields = (fields: { - [key: string]: { allowedValues?: Array<{}>; defaultValue?: {} }; + [key: string]: { + allowedValues?: Array<{}>; + defaultValue?: {}; + required: boolean; + schema: FieldSchema; + }; }) => Object.keys(fields ?? {}).reduce((fieldsAcc, fieldKey) => { return { ...fieldsAcc, [fieldKey]: { + required: fields[fieldKey]?.required, allowedValues: fields[fieldKey]?.allowedValues ?? [], defaultValue: fields[fieldKey]?.defaultValue ?? {}, + schema: fields[fieldKey]?.schema, }, }; }, {}); @@ -326,7 +335,6 @@ export const createExternalService = ( const getIssueTypes = async () => { const capabilitiesResponse = await getCapabilities(); const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse); - try { if (!supportsNewAPI) { const res = await request({ @@ -366,7 +374,6 @@ export const createExternalService = ( const getFieldsByIssueType = async (issueTypeId: string) => { const capabilitiesResponse = await getCapabilities(); const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse); - try { if (!supportsNewAPI) { const res = await request({ @@ -378,6 +385,7 @@ export const createExternalService = ( }); const fields = res.data.projects[0]?.issuetypes[0]?.fields || {}; + return normalizeFields(fields); } else { const res = await request({ @@ -409,6 +417,30 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const issueTypes = await getIssueTypes(); + const fieldsPerIssueType = await Promise.all( + issueTypes.map((issueType) => getFieldsByIssueType(issueType.id)) + ); + return fieldsPerIssueType.reduce((acc: GetCommonFieldsResponse, fieldTypesByIssue) => { + const currentListOfFields = Object.keys(acc); + return currentListOfFields.length === 0 + ? fieldTypesByIssue + : currentListOfFields.reduce( + (add: GetCommonFieldsResponse, field) => + Object.keys(fieldTypesByIssue).includes(field) + ? { ...add, [field]: acc[field] } + : add, + {} + ); + }, {}); + } catch (error) { + // errors that happen here would be thrown in the contained async calls + throw error; + } + }; + const getIssues = async (title: string) => { const query = `${searchUrl}?jql=${encodeURIComponent( `project="${projectKey}" and summary ~"${title}"` @@ -461,6 +493,7 @@ export const createExternalService = ( }; return { + getFields, getIncident, createIncident, updateIncident, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts index 7d650a22fba1b..e142637010a98 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts @@ -79,11 +79,34 @@ export interface CreateCommentParams { comment: Comment; } +export interface FieldsSchema { + type: string; + [key: string]: string; +} + +export interface ExternalServiceFields { + clauseNames: string[]; + custom: boolean; + id: string; + key: string; + name: string; + navigatable: boolean; + orderable: boolean; + schema: FieldsSchema; + searchable: boolean; +} + export type GetIssueTypesResponse = Array<{ id: string; name: string }>; + +export interface FieldSchema { + type: string; + items?: string; +} export type GetFieldsByIssueTypeResponse = Record< string, - { allowedValues: Array<{}>; defaultValue: {} } + { allowedValues: Array<{}>; defaultValue: {}; required: boolean; schema: FieldSchema } >; +export type GetCommonFieldsResponse = GetFieldsByIssueTypeResponse; export type GetIssuesResponse = Array<{ id: string; key: string; title: string }>; export interface GetIssueResponse { @@ -93,15 +116,16 @@ export interface GetIssueResponse { } export interface ExternalService { - getIncident: (id: string) => Promise; - createIncident: (params: CreateIncidentParams) => Promise; - updateIncident: (params: UpdateIncidentParams) => Promise; createComment: (params: CreateCommentParams) => Promise; + createIncident: (params: CreateIncidentParams) => Promise; + getFields: () => Promise; getCapabilities: () => Promise; - getIssueTypes: () => Promise; getFieldsByIssueType: (issueTypeId: string) => Promise; - getIssues: (title: string) => Promise; + getIncident: (id: string) => Promise; getIssue: (id: string) => Promise; + getIssues: (title: string) => Promise; + getIssueTypes: () => Promise; + updateIncident: (params: UpdateIncidentParams) => Promise; } export interface PushToServiceApiParams extends ExecutorSubActionPushParams { @@ -157,6 +181,11 @@ export interface GetIssueTypesHandlerArgs { params: ExecutorSubActionGetIssueTypesParams; } +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionGetIssueTypesParams; +} + export interface GetFieldsByIssueTypeHandlerArgs { externalService: ExternalService; params: ExecutorSubActionGetFieldsByIssueTypeParams; @@ -177,15 +206,16 @@ export interface GetIssueHandlerArgs { } export interface ExternalServiceApi { - handshake: (args: HandshakeApiHandlerArgs) => Promise; - pushToService: (args: PushToServiceApiHandlerArgs) => Promise; + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; + handshake: (args: HandshakeApiHandlerArgs) => Promise; issueTypes: (args: GetIssueTypesHandlerArgs) => Promise; + pushToService: (args: PushToServiceApiHandlerArgs) => Promise; fieldsByIssueType: ( args: GetFieldsByIssueTypeHandlerArgs ) => Promise; - issues: (args: GetIssuesHandlerArgs) => Promise; issue: (args: GetIssueHandlerArgs) => Promise; + issues: (args: GetIssuesHandlerArgs) => Promise; } export type JiraExecutorResultData = diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts index 46d9c114297a9..29f2594d2b6f8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts @@ -15,6 +15,7 @@ import { GetSeverityHandlerArgs, PushToServiceApiParams, PushToServiceResponse, + GetCommonFieldsHandlerArgs, } from './types'; // TODO: to remove, need to support Case @@ -32,6 +33,10 @@ const getIncidentHandler = async ({ params, }: GetIncidentApiHandlerArgs) => {}; +const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => { + const res = await externalService.getFields(); + return res; +}; const getIncidentTypesHandler = async ({ externalService }: GetIncidentTypesHandlerArgs) => { const res = await externalService.getIncidentTypes(); return res; @@ -136,9 +141,10 @@ const pushToServiceHandler = async ({ }; export const api: ExternalServiceApi = { - handshake: handshakeHandler, - pushToService: pushToServiceHandler, + getFields: getFieldsHandler, getIncident: getIncidentHandler, + handshake: handshakeHandler, incidentTypes: getIncidentTypesHandler, + pushToService: pushToServiceHandler, severity: getSeverityHandler, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts index 53285a2a350af..6203dda4120f5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts @@ -25,6 +25,7 @@ import { ResilientExecutorResultData, ExecutorSubActionGetIncidentTypesParams, ExecutorSubActionGetSeverityParams, + ExecutorSubActionCommonFieldsParams, } from './types'; import * as i18n from './translations'; import { Logger } from '../../../../../../src/core/server'; @@ -37,7 +38,7 @@ interface GetActionTypeParams { configurationUtilities: ActionsConfigurationUtilities; } -const supportedSubActions: string[] = ['pushToService', 'incidentTypes', 'severity']; +const supportedSubActions: string[] = ['getFields', 'pushToService', 'incidentTypes', 'severity']; // action type definition export function getActionType( @@ -122,6 +123,14 @@ async function executor( logger.debug(`response push to service for incident id: ${data.id}`); } + if (subAction === 'getFields') { + const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams; + data = await api.getFields({ + externalService, + params: getFieldsParams, + }); + } + if (subAction === 'incidentTypes') { const incidentTypesParams = subActionParams as ExecutorSubActionGetIncidentTypesParams; data = await api.incidentTypes({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts index 2e841728159a3..2b2a22a66b709 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts @@ -8,8 +8,275 @@ import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } import { MapRecord } from '../case/types'; +export const resilientFields = [ + { + id: 17, + name: 'name', + text: 'Name', + prefix: null, + type_id: 0, + tooltip: 'A unique name to identify this particular incident.', + input_type: 'text', + required: 'always', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: 'ad6ed4f2-8d87-4ba2-81fa-03568a9326cc', + operations: [ + 'equals', + 'not_equals', + 'contains', + 'not_contains', + 'changed', + 'changed_to', + 'not_changed_to', + 'has_a_value', + 'not_has_a_value', + ], + operation_perms: { + changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + changed: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['text'], + sort: true, + }, + read_only: false, + changeable: true, + rich_text: false, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, + { + id: 15, + name: 'description', + text: 'Description', + prefix: null, + type_id: 0, + tooltip: 'A free form text description of the incident.', + input_type: 'textarea', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: '420d70b1-98f9-4681-a20b-84f36a9e5e48', + operations: [ + 'equals', + 'not_equals', + 'contains', + 'not_contains', + 'changed', + 'changed_to', + 'not_changed_to', + 'has_a_value', + 'not_has_a_value', + ], + operation_perms: { + changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_changed_to: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + changed: { + show_in_manual_actions: false, + show_in_auto_actions: true, + show_in_notifications: true, + }, + contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_contains: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_equals: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['textarea'], + sort: true, + }, + read_only: false, + changeable: true, + rich_text: true, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, + { + id: 65, + name: 'create_date', + text: 'Date Created', + prefix: null, + type_id: 0, + tooltip: 'The date the incident was created. This field is read-only.', + input_type: 'datetimepicker', + hide_notification: false, + chosen: false, + default_chosen_by_server: false, + blank_option: false, + internal: true, + uuid: 'b4faf728-881a-4e8b-bf0b-d39b720392a1', + operations: ['due_within', 'overdue_by', 'has_a_value', 'not_has_a_value'], + operation_perms: { + has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + not_has_a_value: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + due_within: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + overdue_by: { + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + }, + }, + values: [], + perms: { + delete: false, + modify_name: false, + modify_values: false, + modify_blank: false, + modify_required: false, + modify_operations: false, + modify_chosen: false, + modify_default: false, + show_in_manual_actions: true, + show_in_auto_actions: true, + show_in_notifications: true, + show_in_scripts: true, + modify_type: ['datetimepicker'], + sort: true, + }, + read_only: true, + changeable: false, + rich_text: false, + templates: [], + deprecated: false, + tags: [], + calculated: false, + is_tracked: false, + allow_default_value: false, + }, +]; + const createMock = (): jest.Mocked => { const service = { + getFields: jest.fn().mockImplementation(() => Promise.resolve(resilientFields)), getIncident: jest.fn().mockImplementation(() => Promise.resolve({ id: '1', diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts index b6e3a9525dfd4..c7ceba94140fb 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts @@ -53,11 +53,16 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ }); // Reserved for future implementation +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionGetIncidentTypesParamsSchema = schema.object({}); export const ExecutorSubActionGetSeverityParamsSchema = schema.object({}); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts index 86ea352625a5b..ecf246cb8fe3c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts @@ -11,7 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; -import { incidentTypes, severity } from './mocks'; +import { incidentTypes, resilientFields, severity } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -231,7 +231,7 @@ describe('IBM Resilient service', () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( 'Unable to get incident with id 1. Error: An error has occurred' ); }); @@ -310,7 +310,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createIncident({ incident: { name: 'title', @@ -418,7 +418,7 @@ describe('IBM Resilient service', () => { test('it should throw an error', async () => { mockIncidentUpdate(true); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { @@ -502,7 +502,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createComment({ incidentId: '1', comment: { @@ -541,7 +541,7 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect(service.getIncidentTypes()).rejects.toThrow( + await expect(service.getIncidentTypes()).rejects.toThrow( '[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.' ); }); @@ -578,9 +578,40 @@ describe('IBM Resilient service', () => { throw new Error('An error has occurred'); }); - expect(service.getIncidentTypes()).rejects.toThrow( + await expect(service.getIncidentTypes()).rejects.toThrow( '[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.' ); }); }); + + describe('getFields', () => { + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: resilientFields, + })); + await service.getFields(); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + url: 'https://resilient.elastic.co/rest/orgs/201/types/incident/fields', + }); + }); + test('it returns common fields correctly', async () => { + requestMock.mockImplementation(() => ({ + data: resilientFields, + })); + const res = await service.getFields(); + expect(res).toEqual(resilientFields); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getFields()).rejects.toThrow( + 'Unable to get fields. Error: An error has occurred' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts index 4bf1453641e42..a13204f8bb1d8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts @@ -303,12 +303,27 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const res = await request({ + axios: axiosInstance, + url: incidentFieldsUrl, + logger, + proxySettings, + }); + return res.data ?? []; + } catch (error) { + throw new Error(getErrorMessage(i18n.NAME, `Unable to get fields. Error: ${error.message}.`)); + } + }; + return { - getIncident, - createIncident, - updateIncident, createComment, + createIncident, + getFields, + getIncident, getIncidentTypes, getSeverity, + updateIncident, }; }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts index ed622ee473b65..a70420b30a092 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts @@ -8,14 +8,15 @@ import { TypeOf } from '@kbn/config-schema'; import { - ExternalIncidentServiceConfigurationSchema, - ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, - ExecutorSubActionPushParamsSchema, + ExecutorSubActionCommonFieldsParamsSchema, ExecutorSubActionGetIncidentParamsSchema, - ExecutorSubActionHandshakeParamsSchema, ExecutorSubActionGetIncidentTypesParamsSchema, ExecutorSubActionGetSeverityParamsSchema, + ExecutorSubActionHandshakeParamsSchema, + ExecutorSubActionPushParamsSchema, + ExternalIncidentServiceConfigurationSchema, + ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; @@ -31,6 +32,10 @@ export type ResilientSecretConfigurationType = TypeOf< typeof ExternalIncidentServiceSecretConfigurationSchema >; +export type ExecutorSubActionCommonFieldsParams = TypeOf< + typeof ExecutorSubActionCommonFieldsParamsSchema +>; + export type ExecutorParams = TypeOf; export type ExecutorSubActionPushParams = TypeOf; @@ -60,6 +65,14 @@ export interface ExternalServiceCommentResponse { } export type ExternalServiceParams = Record; +export interface ExternalServiceFields { + id: string; + input_type: string; + name: string; + read_only: boolean; + required?: string; +} +export type GetCommonFieldsResponse = ExternalServiceFields[]; export type Incident = Pick< ExecutorSubActionPushParams, @@ -86,12 +99,13 @@ export type GetIncidentTypesResponse = Array<{ id: string; name: string }>; export type GetSeverityResponse = Array<{ id: string; name: string }>; export interface ExternalService { - getIncident: (id: string) => Promise; - createIncident: (params: CreateIncidentParams) => Promise; - updateIncident: (params: UpdateIncidentParams) => Promise; createComment: (params: CreateCommentParams) => Promise; + createIncident: (params: CreateIncidentParams) => Promise; + getFields: () => Promise; + getIncident: (id: string) => Promise; getIncidentTypes: () => Promise; getSeverity: () => Promise; + updateIncident: (params: UpdateIncidentParams) => Promise; } export interface PushToServiceApiParams extends ExecutorSubActionPushParams { @@ -132,6 +146,11 @@ export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: ExecutorSubActionHandshakeParams; } +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionCommonFieldsParams; +} + export interface GetIncidentTypesHandlerArgs { externalService: ExternalService; params: ExecutorSubActionGetIncidentTypesParams; @@ -147,6 +166,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse { } export interface ExternalServiceApi { + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; @@ -156,6 +176,7 @@ export interface ExternalServiceApi { export type ResilientExecutorResultData = | PushToServiceResponse + | GetCommonFieldsResponse | GetIncidentTypesResponse | GetSeverityResponse; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts index d49c2f265d04f..4683b661e21da 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts @@ -5,7 +5,7 @@ */ import { Logger } from '../../../../../../src/core/server'; -import { externalServiceMock, mapping, apiParams } from './mocks'; +import { externalServiceMock, mapping, apiParams, serviceNowCommonFields } from './mocks'; import { ExternalService } from './types'; import { api } from './api'; let mockedLogger: jest.Mocked; @@ -15,634 +15,619 @@ describe('api', () => { beforeEach(() => { externalService = externalServiceMock.create(); - jest.clearAllMocks(); }); - afterEach(() => { - jest.clearAllMocks(); - }); + describe('create incident', () => { + test('it creates an incident', async () => { + const params = { ...apiParams, externalId: null }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); - describe('pushToService', () => { - describe('create incident', () => { - test('it creates an incident', async () => { - const params = { ...apiParams, externalId: null }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'INC01', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - ], - }); - }); - - test('it creates an incident without comments', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-1', - title: 'INC01', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - }); - }); - - test('it calls createIncident correctly', async () => { - const params = { ...apiParams, externalId: null, comments: [] }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: { username: 'elastic', password: 'elastic' }, - logger: mockedLogger, - }); - - expect(externalService.createIncident).toHaveBeenCalledWith({ - incident: { - severity: '1', - urgency: '2', - impact: '3', - caller_id: 'elastic', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - expect(externalService.updateIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident correctly when creating an incident and having comments', async () => { - const params = { ...apiParams, externalId: null }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(2); - expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + expect(res).toEqual({ + id: 'incident-1', + title: 'INC01', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + comments: [ + { + commentId: 'case-comment-1', + pushedDate: '2020-03-10T12:24:20.000Z', }, - incidentId: 'incident-1', - }); - - expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + { + commentId: 'case-comment-2', + pushedDate: '2020-03-10T12:24:20.000Z', }, - incidentId: 'incident-1', - }); + ], }); }); - describe('update incident', () => { - test('it updates an incident', async () => { - const res = await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-2', - title: 'INC02', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - comments: [ - { - commentId: 'case-comment-1', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - { - commentId: 'case-comment-2', - pushedDate: '2020-03-10T12:24:20.000Z', - }, - ], - }); - }); - - test('it updates an incident without comments', async () => { - const params = { ...apiParams, comments: [] }; - const res = await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(res).toEqual({ - id: 'incident-2', - title: 'INC02', - pushedDate: '2020-03-10T12:24:20.000Z', - url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', - }); - }); - - test('it calls updateIncident correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - expect(externalService.createIncident).not.toHaveBeenCalled(); - }); - - test('it calls updateIncident to create a comments correctly', async () => { - const params = { ...apiParams }; - await api.pushToService({ - externalService, - mapping, - params, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(3); - expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - incidentId: 'incident-3', - }); - - expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { - incident: { - severity: '1', - urgency: '2', - impact: '3', - comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - incidentId: 'incident-2', - }); + test('it creates an incident without comments', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-1', + title: 'INC01', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', }); }); - describe('mapping variations', () => { - test('overwrite & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('append & append', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'append', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - }, - }); - }); - - test('overwrite & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('overwrite & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('nothing & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'nothing', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'nothing', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - }, - }); - }); - - test('append & overwrite', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'overwrite', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', - description: - 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + test('it calls createIncident correctly', async () => { + const params = { ...apiParams, externalId: null, comments: [] }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: { username: 'elastic', password: 'elastic' }, + logger: mockedLogger, + }); + + expect(externalService.createIncident).toHaveBeenCalledWith({ + incident: { + severity: '1', + urgency: '2', + impact: '3', + caller_id: 'elastic', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + expect(externalService.updateIncident).not.toHaveBeenCalled(); + }); + + test('it calls updateIncident correctly when creating an incident and having comments', async () => { + const params = { ...apiParams, externalId: null }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(2); + expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-1', + }); + + expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-1', + }); + }); + }); + + describe('update incident', () => { + test('it updates an incident', async () => { + const res = await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + comments: [ + { + commentId: 'case-comment-1', + pushedDate: '2020-03-10T12:24:20.000Z', }, - }); - }); - - test('append & nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'append', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'append', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'append', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledWith({ - incidentId: 'incident-3', - incident: { - severity: '1', - urgency: '2', - impact: '3', - short_description: - 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + { + commentId: 'case-comment-2', + pushedDate: '2020-03-10T12:24:20.000Z', }, - }); - }); - - test('comment nothing', async () => { - mapping.set('title', { - target: 'short_description', - actionType: 'overwrite', - }); - - mapping.set('description', { - target: 'description', - actionType: 'nothing', - }); - - mapping.set('comments', { - target: 'comments', - actionType: 'nothing', - }); - - mapping.set('short_description', { - target: 'title', - actionType: 'overwrite', - }); - - await api.pushToService({ - externalService, - mapping, - params: apiParams, - secrets: {}, - logger: mockedLogger, - }); - expect(externalService.updateIncident).toHaveBeenCalledTimes(1); + ], + }); + }); + + test('it updates an incident without comments', async () => { + const params = { ...apiParams, comments: [] }; + const res = await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(res).toEqual({ + id: 'incident-2', + title: 'INC02', + pushedDate: '2020-03-10T12:24:20.000Z', + url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123', + }); + }); + + test('it calls updateIncident correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + expect(externalService.createIncident).not.toHaveBeenCalled(); + }); + + test('it calls updateIncident to create a comments correctly', async () => { + const params = { ...apiParams }; + await api.pushToService({ + externalService, + mapping, + params, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(3); + expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-3', + }); + + expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, { + incident: { + severity: '1', + urgency: '2', + impact: '3', + comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + incidentId: 'incident-2', + }); + }); + }); + + describe('mapping variations', () => { + test('overwrite & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & append', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'append', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: + 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + }, + }); + }); + + test('overwrite & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('overwrite & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('nothing & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'nothing', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'nothing', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & overwrite', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'overwrite', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('append & nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'append', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'append', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'append', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledWith({ + incidentId: 'incident-3', + incident: { + severity: '1', + urgency: '2', + impact: '3', + short_description: + 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + }, + }); + }); + + test('comment nothing', async () => { + mapping.set('title', { + target: 'short_description', + actionType: 'overwrite', + }); + + mapping.set('description', { + target: 'description', + actionType: 'nothing', + }); + + mapping.set('comments', { + target: 'comments', + actionType: 'nothing', + }); + + mapping.set('short_description', { + target: 'title', + actionType: 'overwrite', + }); + + await api.pushToService({ + externalService, + mapping, + params: apiParams, + secrets: {}, + logger: mockedLogger, + }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(1); + }); + }); + + describe('getFields', () => { + test('it returns the fields correctly', async () => { + const res = await api.getFields({ + externalService, + params: {}, }); + expect(res).toEqual(serviceNowCommonFields); }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts index 6d12a3c92dac7..fbd8fdd635d70 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts @@ -12,6 +12,8 @@ import { PushToServiceApiParams, PushToServiceResponse, Incident, + GetCommonFieldsHandlerArgs, + GetCommonFieldsResponse, } from './types'; // TODO: to remove, need to support Case @@ -127,8 +129,16 @@ const pushToServiceHandler = async ({ return res; }; +const getFieldsHandler = async ({ + externalService, +}: GetCommonFieldsHandlerArgs): Promise => { + const res = await externalService.getFields(); + return res; +}; + export const api: ExternalServiceApi = { + getFields: getFieldsHandler, + getIncident: getIncidentHandler, handshake: handshakeHandler, pushToService: pushToServiceHandler, - getIncident: getIncidentHandler, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index 41a577918b18e..d1182b0d3b2fa 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -25,6 +25,8 @@ import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, PushToServiceResponse, + ExecutorSubActionCommonFieldsParams, + ServiceNowExecutorResultData, } from './types'; // TODO: to remove, need to support Case @@ -63,7 +65,7 @@ export function getActionType( } // action executor - +const supportedSubActions: string[] = ['getFields', 'pushToService']; async function executor( { logger }: { logger: Logger }, execOptions: ActionTypeExecutorOptions< @@ -71,10 +73,10 @@ async function executor( ServiceNowSecretConfigurationType, ExecutorParams > -): Promise> { +): Promise> { const { actionId, config, params, secrets } = execOptions; const { subAction, subActionParams } = params; - let data: PushToServiceResponse | null = null; + let data: ServiceNowExecutorResultData | null = null; const externalService = createExternalService( { @@ -91,7 +93,7 @@ async function executor( throw new Error(errorMessage); } - if (subAction !== 'pushToService') { + if (!supportedSubActions.includes(subAction)) { const errorMessage = `[Action][ExternalService] subAction ${subAction} not implemented.`; logger.error(errorMessage); throw new Error(errorMessage); @@ -117,5 +119,13 @@ async function executor( logger.debug(`response push to service for incident id: ${data.id}`); } + if (subAction === 'getFields') { + const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams; + data = await api.getFields({ + externalService, + params: getFieldsParams, + }); + } + return { status: 'ok', data: data ?? {}, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts index 7c2b1bd9d73c1..2351be36a50c4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts @@ -7,8 +7,36 @@ import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types'; import { MapRecord } from '../case/types'; +export const serviceNowCommonFields = [ + { + column_label: 'Close notes', + max_length: '4000', + element: 'close_notes', + }, + { + column_label: 'Description', + max_length: '4000', + element: 'description', + }, + { + column_label: 'Short description', + max_length: '160', + element: 'short_description', + }, + { + column_label: 'Created by', + max_length: '40', + element: 'sys_created_by', + }, + { + column_label: 'Updated by', + max_length: '40', + element: 'sys_updated_by', + }, +]; const createMock = (): jest.Mocked => { const service = { + getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)), getIncident: jest.fn().mockImplementation(() => Promise.resolve({ short_description: 'title from servicenow', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts index 0dd70ea36636e..77c48aab1f309 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts @@ -28,6 +28,7 @@ export const ExternalIncidentServiceSecretConfigurationSchema = schema.object( ); export const ExecutorSubActionSchema = schema.oneOf([ + schema.literal('getFields'), schema.literal('getIncident'), schema.literal('pushToService'), schema.literal('handshake'), @@ -53,8 +54,13 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ // Reserved for future implementation export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); +export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); export const ExecutorParamsSchema = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), schema.object({ subAction: schema.literal('getIncident'), subActionParams: ExecutorSubActionGetIncidentParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts index 2adcdf561ce17..8ec80be1e2b09 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts @@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { serviceNowCommonFields } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); @@ -108,7 +109,7 @@ describe('ServiceNow service', () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); }); - expect(service.getIncident('1')).rejects.toThrow( + await expect(service.getIncident('1')).rejects.toThrow( 'Unable to get incident with id 1. Error: An error has occurred' ); }); @@ -155,7 +156,7 @@ describe('ServiceNow service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.createIncident({ incident: { short_description: 'title', description: 'desc' }, }) @@ -207,7 +208,7 @@ describe('ServiceNow service', () => { throw new Error('An error has occurred'); }); - expect( + await expect( service.updateIncident({ incidentId: '1', incident: { short_description: 'title', description: 'desc' }, @@ -234,4 +235,36 @@ describe('ServiceNow service', () => { }); }); }); + + describe('getFields', () => { + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowCommonFields }, + })); + await service.getFields(); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + url: + 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label', + }); + }); + test('it returns common fields correctly', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowCommonFields }, + })); + const res = await service.getFields(); + expect(res).toEqual(serviceNowCommonFields); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getFields()).rejects.toThrow( + 'Unable to get common fields. Error: An error has occurred' + ); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 9b1da4b4007c6..57f7176e2353c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -16,6 +16,7 @@ import { ProxySettings } from '../../types'; const API_VERSION = 'v2'; const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; +const SYS_DICTIONARY = `api/now/${API_VERSION}/table/sys_dictionary`; // Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`; @@ -33,6 +34,7 @@ export const createExternalService = ( } const incidentUrl = `${url}/${INCIDENT_URL}`; + const fieldsUrl = `${url}/${SYS_DICTIONARY}?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label`; const axiosInstance = axios.create({ auth: { username, password }, }); @@ -126,10 +128,28 @@ export const createExternalService = ( } }; + const getFields = async () => { + try { + const res = await request({ + axios: axiosInstance, + url: fieldsUrl, + logger, + proxySettings, + }); + + return res.data.result.length > 0 ? res.data.result : []; + } catch (error) { + throw new Error( + getErrorMessage(i18n.NAME, `Unable to get common fields. Error: ${error.message}`) + ); + } + }; + return { - getIncident, createIncident, - updateIncident, findIncidents, + getFields, + getIncident, + updateIncident, }; }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index a6a0ac946fe96..0ee03f883ec05 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -8,12 +8,13 @@ import { TypeOf } from '@kbn/config-schema'; import { - ExternalIncidentServiceConfigurationSchema, - ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, - ExecutorSubActionPushParamsSchema, + ExecutorSubActionCommonFieldsParamsSchema, ExecutorSubActionGetIncidentParamsSchema, ExecutorSubActionHandshakeParamsSchema, + ExecutorSubActionPushParamsSchema, + ExternalIncidentServiceConfigurationSchema, + ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; import { ExternalServiceCommentResponse } from '../case/types'; @@ -27,6 +28,12 @@ export type ServiceNowSecretConfigurationType = TypeOf< typeof ExternalIncidentServiceSecretConfigurationSchema >; +export type ExecutorSubActionCommonFieldsParams = TypeOf< + typeof ExecutorSubActionCommonFieldsParamsSchema +>; + +export type ServiceNowExecutorResultData = PushToServiceResponse | GetCommonFieldsResponse; + export interface CreateCommentRequest { [key: string]: string; } @@ -59,6 +66,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse { export type ExternalServiceParams = Record; export interface ExternalService { + getFields: () => Promise; getIncident: (id: string) => Promise; createIncident: (params: ExternalServiceParams) => Promise; updateIncident: (params: ExternalServiceParams) => Promise; @@ -102,8 +110,24 @@ export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: ExecutorSubActionHandshakeParams; } +export interface ExternalServiceFields { + column_label: string; + name: string; + internal_type: { + link: string; + value: string; + }; + max_length: string; + element: string; +} +export type GetCommonFieldsResponse = ExternalServiceFields[]; +export interface GetCommonFieldsHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionCommonFieldsParams; +} export interface ExternalServiceApi { + getFields: (args: GetCommonFieldsHandlerArgs) => Promise; handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; getIncident: (args: GetIncidentApiHandlerArgs) => Promise; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts index 39f64dd037945..4e9293b74c99e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts @@ -333,7 +333,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -351,7 +351,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -374,7 +374,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -402,7 +402,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); @@ -430,7 +430,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [issueTypes]\n- [4.subAction]: expected value to equal [fieldsByIssueType]\n- [5.subAction]: expected value to equal [issues]\n- [6.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts index 5d54ea99889c1..34c1c757ab119 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts @@ -334,7 +334,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -352,7 +352,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -375,7 +375,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -403,7 +403,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); @@ -431,7 +431,7 @@ export default function resilientTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [3.subAction]: expected value to equal [incidentTypes]\n- [4.subAction]: expected value to equal [severity]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [incidentTypes]\n- [5.subAction]: expected value to equal [severity]', }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index 0684707c73824..5b4db53a59a41 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -328,7 +328,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subAction]: expected value to equal [pushToService]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]', }); }); }); @@ -346,7 +346,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]', }); }); }); @@ -369,7 +369,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.title]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.title]: expected value of type [string] but got [undefined]', }); }); }); @@ -397,7 +397,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments.0.commentId]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments.0.commentId]: expected value of type [string] but got [undefined]', }); }); }); @@ -425,7 +425,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getIncident]\n- [1.subAction]: expected value to equal [handshake]\n- [2.subActionParams.comments.0.comment]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments.0.comment]: expected value of type [string] but got [undefined]', }); }); }); From fdb9d76fbda8e1dd0cc9fd43c903c71702d77ad7 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Mon, 9 Nov 2020 10:31:21 -0700 Subject: [PATCH 27/87] Uses asCurrentUser in getClusterUuid (#82908) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../usage_collection/server/routes/stats/stats.ts | 14 +++++++++----- .../api_integration/apis/kibana/stats/stats.js | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts index ef64d15fabc2d..d38250067053c 100644 --- a/src/plugins/usage_collection/server/routes/stats/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats/stats.ts @@ -73,8 +73,9 @@ export function registerStatsRoute({ return collectorSet.toObject(usage); }; - const getClusterUuid = async (callCluster: LegacyAPICaller): Promise => { - const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' }); + const getClusterUuid = async (asCurrentUser: ElasticsearchClient): Promise => { + const { body } = await asCurrentUser.info({ filter_path: 'cluster_uuid' }); + const { cluster_uuid: uuid } = body; return uuid; }; @@ -103,7 +104,7 @@ export function registerStatsRoute({ let extended; if (isExtended) { const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; - const esClient = context.core.elasticsearch.client.asCurrentUser; + const { asCurrentUser } = context.core.elasticsearch.client; const savedObjectsClient = context.core.savedObjects.client; if (shouldGetUsage) { @@ -114,9 +115,12 @@ export function registerStatsRoute({ } const usagePromise = shouldGetUsage - ? getUsage(callCluster, esClient, savedObjectsClient) + ? getUsage(callCluster, asCurrentUser, savedObjectsClient) : Promise.resolve({}); - const [usage, clusterUuid] = await Promise.all([usagePromise, getClusterUuid(callCluster)]); + const [usage, clusterUuid] = await Promise.all([ + usagePromise, + getClusterUuid(asCurrentUser), + ]); let modifiedUsage = usage; if (isLegacy) { diff --git a/x-pack/test/api_integration/apis/kibana/stats/stats.js b/x-pack/test/api_integration/apis/kibana/stats/stats.js index f0a41f1f008ba..ae4ddad66863b 100644 --- a/x-pack/test/api_integration/apis/kibana/stats/stats.js +++ b/x-pack/test/api_integration/apis/kibana/stats/stats.js @@ -30,7 +30,7 @@ export default function ({ getService }) { }); it('should return 401 for extended', async () => { - await supertestNoAuth.get('/api/stats?extended').expect(401); + await supertestNoAuth.get('/api/stats?extended').auth(null, null).expect(401); }); }); From 83e51f56885f9629d6849ce5bad2c2569b6eb91f Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 9 Nov 2020 11:46:36 -0600 Subject: [PATCH 28/87] add alternate path for x-pack/Cloud test for Lens (#82634) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/management/_scripted_fields.js | 152 +++++++++++------- 1 file changed, 93 insertions(+), 59 deletions(-) diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 6da9ebed0538a..5ca01f239e762 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -198,35 +198,44 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - const expectedChartValues = [ - ['14', '31'], - ['10', '29'], - ['7', '24'], - ['11', '24'], - ['12', '23'], - ['20', '23'], - ['19', '21'], - ['6', '20'], - ['17', '20'], - ['30', '20'], - ['13', '19'], - ['18', '18'], - ['16', '17'], - ['5', '16'], - ['8', '16'], - ['15', '14'], - ['3', '13'], - ['2', '12'], - ['9', '10'], - ['4', '9'], - ]; await filterBar.removeAllFilters(); await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData(expectedChartValues); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + const expectedChartValues = [ + ['14', '31'], + ['10', '29'], + ['7', '24'], + ['11', '24'], + ['12', '23'], + ['20', '23'], + ['19', '21'], + ['6', '20'], + ['17', '20'], + ['30', '20'], + ['13', '19'], + ['18', '18'], + ['16', '17'], + ['5', '16'], + ['8', '16'], + ['15', '14'], + ['3', '13'], + ['2', '12'], + ['9', '10'], + ['4', '9'], + ]; + + await inspector.open(); + await inspector.setTablePageSize(50); + await inspector.expectTableData(expectedChartValues); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Average of ram_Pain1' + ); + } }); }); @@ -309,11 +318,19 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.expectTableData([ - ['good', '359'], - ['bad', '27'], - ]); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.expectTableData([ + ['good', '359'], + ['bad', '27'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Top values of painString' + ); + } }); }); @@ -397,11 +414,19 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.expectTableData([ - ['true', '359'], - ['false', '27'], - ]); + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.expectTableData([ + ['true', '359'], + ['false', '27'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'Top values of painBool' + ); + } }); }); @@ -488,30 +513,39 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData([ - ['2015-09-17 20:00', '1'], - ['2015-09-17 21:00', '1'], - ['2015-09-17 23:00', '1'], - ['2015-09-18 00:00', '1'], - ['2015-09-18 03:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ]); + + if (await PageObjects.common.isOss()) { + // OSS renders a vertical bar chart and we check the data in the Inspect panel + await inspector.open(); + await inspector.setTablePageSize(50); + await inspector.expectTableData([ + ['2015-09-17 20:00', '1'], + ['2015-09-17 21:00', '1'], + ['2015-09-17 23:00', '1'], + ['2015-09-18 00:00', '1'], + ['2015-09-18 03:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 04:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 05:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 06:00', '1'], + ['2015-09-18 07:00', '1'], + ['2015-09-18 07:00', '1'], + ['2015-09-18 07:00', '1'], + ]); + } else { + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + 'painDate' + ); + } }); }); }); From 48dbf9d6535aa3aca01f6215b12ef424f24020a8 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 9 Nov 2020 14:16:18 -0500 Subject: [PATCH 29/87] Add captions to user and space grid pages (#82713) * Add captions to user and space grid pages * Address PR feedback: reword captions * remove unused i18n values Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/management/users/users_grid/users_grid_page.tsx | 4 ++++ .../spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap | 2 ++ .../spaces/public/management/spaces_grid/spaces_grid_page.tsx | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx index 2b7ed0079f291..37747f9a1ccfa 100644 --- a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx @@ -260,6 +260,10 @@ export class UsersGridPage extends Component { { diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index b40f34273d99f..68c4a4ff02b94 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -110,6 +110,10 @@ export class SpacesGridPage extends Component { Date: Mon, 9 Nov 2020 14:43:44 -0500 Subject: [PATCH 30/87] Add alerting as codeowners to related documentation folder (#82777) * Add alerting as codeowners to related docs folders * Move up Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 84076f4c4fbe9..6da2d5d602186 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -299,6 +299,8 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services +/docs/user/alerting/ @elastic/kibana-alerting-services +/docs/management/alerting/ @elastic/kibana-alerting-services #CC# /x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services #CC# /x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services #CC# /x-pack/legacy/plugins/task_manager @elastic/kibana-alerting-services From 729631a76d054d891d4de1ca7fef79de3aa3526b Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 9 Nov 2020 11:56:56 -0800 Subject: [PATCH 31/87] Update 8.0 breaking change template to gather information on how to programmatically detect it. (#82905) --- .github/ISSUE_TEMPLATE/v8_breaking_change.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md index a64ce33b8f976..86e321990d05f 100644 --- a/.github/ISSUE_TEMPLATE/v8_breaking_change.md +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -30,6 +30,8 @@ requesting the breaking change to be surfaced in the Upgrade Assistant. +**How can we programmatically determine whether the cluster is affected by this breaking change?** + **What can users do to address the change manually?** From c6afc47f32e4630d032a7cf4a4c3ee02660ef40b Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Nov 2020 13:01:22 -0700 Subject: [PATCH 32/87] Remove data <--> expressions circular dependencies. (#82685) --- ...gins-data-public.executioncontextsearch.md | 15 ++++ ...ns-data-public.expressionfunctionkibana.md | 11 +++ ...-public.expressionfunctionkibanacontext.md | 11 +++ ...ta-public.expressionvaluesearchcontext.md} | 2 +- ...ugin-plugins-data-public.kibanacontext.md} | 2 +- .../kibana-plugin-plugins-data-public.md | 5 ++ ...na-plugin-plugins-data-public.searchbar.md | 4 +- ...gins-data-server.executioncontextsearch.md | 15 ++++ ...ns-data-server.expressionfunctionkibana.md | 11 +++ ...-server.expressionfunctionkibanacontext.md | 11 +++ ...ta-server.expressionvaluesearchcontext.md} | 2 +- ...ugin-plugins-data-server.kibanacontext.md} | 2 +- .../kibana-plugin-plugins-data-server.md | 5 ++ ...plugin-plugins-data-server.plugin.setup.md | 4 +- ...plugin-plugins-data-server.plugin.start.md | 4 +- ...ins-expressions-public.executioncontext.md | 2 +- ...ic.expressionfunctiondefinitions.kibana.md | 11 --- ...ssionfunctiondefinitions.kibana_context.md | 11 --- ...ns-public.expressionfunctiondefinitions.md | 2 - ...essions-public.expressionfunctionkibana.md | 11 --- ...ressions-public.iexpressionloaderparams.md | 2 +- ...c.iexpressionloaderparams.searchcontext.md | 2 +- ...-expressions-public.kibana_context_name.md | 11 --- ...ibana-plugin-plugins-expressions-public.md | 4 - ...ins-expressions-server.executioncontext.md | 2 +- ...er.expressionfunctiondefinitions.kibana.md | 11 --- ...ssionfunctiondefinitions.kibana_context.md | 11 --- ...ns-server.expressionfunctiondefinitions.md | 2 - ...essions-server.expressionfunctionkibana.md | 11 --- ...-expressions-server.kibana_context_name.md | 11 --- ...ibana-plugin-plugins-expressions-server.md | 4 - ...ublic.uiactionsservice.addtriggeraction.md | 2 +- ...tions-public.uiactionsservice.getaction.md | 2 +- ...blic.uiactionsservice.gettriggeractions.md | 2 +- ...ionsservice.gettriggercompatibleactions.md | 2 +- ...gins-ui_actions-public.uiactionsservice.md | 10 +-- ...-public.uiactionsservice.registeraction.md | 2 +- .../__snapshots__/kibana.test.ts.snap | 0 .../data/common/search/expressions/esaggs.ts | 7 +- .../data/common/search/expressions/index.ts | 3 + .../common/search/expressions}/kibana.test.ts | 8 +- .../common/search/expressions}/kibana.ts | 10 ++- .../search/expressions}/kibana_context.ts | 21 ++--- .../expressions/kibana_context_type.ts} | 12 ++- .../expressions/utils/function_wrapper.ts | 34 ++++++++ .../common/search/expressions/utils/index.ts | 1 + src/plugins/data/common/utils/index.ts | 1 - src/plugins/data/kibana.json | 2 +- src/plugins/data/public/index.ts | 6 ++ src/plugins/data/public/public.api.md | 80 +++++++++++++------ .../search/errors/timeout_error.test.tsx | 2 +- .../data/public/search/expressions/esdsl.ts | 7 +- .../public/search/search_interceptor.test.ts | 2 +- .../data/public/search/search_interceptor.ts | 7 +- .../data/public/search/search_service.ts | 11 ++- src/plugins/data/server/index.ts | 6 ++ src/plugins/data/server/plugin.ts | 2 +- .../data/server/search/search_service.test.ts | 5 +- .../data/server/search/search_service.ts | 28 ++++--- src/plugins/data/server/server.api.md | 71 +++++++++++----- .../expressions/common/execution/execution.ts | 5 +- .../expressions/common/execution/types.ts | 14 ++-- .../expression_functions/specs/index.ts | 6 -- .../specs/tests/var.test.ts | 3 +- .../specs/tests/var_set.test.ts | 3 +- .../common/expression_functions/types.ts | 4 - .../common/expression_types/specs/index.ts | 3 - .../common/service/expressions_services.ts | 3 +- src/plugins/expressions/kibana.json | 3 +- src/plugins/expressions/public/index.ts | 4 - src/plugins/expressions/public/plugin.test.ts | 17 ---- src/plugins/expressions/public/public.api.md | 32 +------- src/plugins/expressions/public/types/index.ts | 4 +- src/plugins/expressions/server/index.ts | 4 - src/plugins/expressions/server/server.api.md | 30 +------ .../common}/abort_utils.test.ts | 26 +++--- .../common}/abort_utils.ts | 8 +- src/plugins/kibana_utils/common/index.ts | 1 + src/plugins/kibana_utils/public/index.ts | 3 + src/plugins/kibana_utils/server/index.ts | 3 + src/plugins/ui_actions/public/public.api.md | 10 +-- .../helpers/timelion_request_handler.ts | 5 +- .../public/timelion_vis_fn.ts | 8 +- .../vis_type_timeseries/public/metrics_fn.ts | 3 +- src/plugins/vis_type_vega/public/vega_fn.ts | 12 +-- x-pack/plugins/canvas/types/state.ts | 2 +- .../search/es_search/es_search_rxjs_utils.ts | 2 +- .../public/search/search_interceptor.test.ts | 3 +- .../public/search/search_interceptor.ts | 4 +- .../editor_frame/suggestion_panel.tsx | 3 +- .../workspace_panel/workspace_panel.tsx | 10 +-- .../embeddable/embeddable.tsx | 2 +- .../embeddable/expression_wrapper.tsx | 2 +- .../editor_frame_service/merge_tables.test.ts | 3 +- .../editor_frame_service/merge_tables.ts | 5 +- .../public/xy_visualization/expression.tsx | 6 +- .../events/last_event_time/index.ts | 2 +- .../containers/matrix_histogram/index.ts | 7 +- .../public/common/containers/source/index.tsx | 2 +- .../common/hooks/eql/use_eql_preview.ts | 2 +- .../containers/authentications/index.tsx | 7 +- .../hosts/containers/hosts/details/_index.tsx | 2 +- .../hosts/first_last_seen/index.tsx | 2 +- .../public/hosts/containers/hosts/index.tsx | 7 +- .../kpi_hosts/authentications/index.tsx | 2 +- .../containers/kpi_hosts/hosts/index.tsx | 2 +- .../containers/kpi_hosts/unique_ips/index.tsx | 2 +- .../containers/uncommon_processes/index.tsx | 7 +- .../network/containers/details/index.tsx | 7 +- .../containers/kpi_network/dns/index.tsx | 2 +- .../kpi_network/network_events/index.tsx | 2 +- .../kpi_network/tls_handshakes/index.tsx | 2 +- .../kpi_network/unique_flows/index.tsx | 2 +- .../kpi_network/unique_private_ips/index.tsx | 2 +- .../network/containers/network_dns/index.tsx | 7 +- .../network/containers/network_http/index.tsx | 7 +- .../network_top_countries/index.tsx | 7 +- .../containers/network_top_n_flow/index.tsx | 7 +- .../public/network/containers/tls/index.tsx | 7 +- .../public/network/containers/users/index.tsx | 7 +- .../containers/overview_host/index.tsx | 7 +- .../containers/overview_network/index.tsx | 7 +- .../translations/translations/ja-JP.json | 12 +-- .../translations/translations/zh-CN.json | 12 +-- 124 files changed, 472 insertions(+), 483 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.executioncontextsearch.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibana.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md rename docs/development/plugins/{expressions/server/kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md => data/public/kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md} (54%) rename docs/development/plugins/{expressions/public/kibana-plugin-plugins-expressions-public.kibanacontext.md => data/public/kibana-plugin-plugins-data-public.kibanacontext.md} (51%) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.executioncontextsearch.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibana.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md rename docs/development/plugins/{expressions/public/kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md => data/server/kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md} (54%) rename docs/development/plugins/{expressions/server/kibana-plugin-plugins-expressions-server.kibanacontext.md => data/server/kibana-plugin-plugins-data-server.kibanacontext.md} (51%) delete mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md delete mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md delete mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md delete mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibana_context_name.md delete mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md delete mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md delete mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md delete mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibana_context_name.md rename src/plugins/{expressions/common/expression_functions/specs/tests => data/common/search/expressions}/__snapshots__/kibana.test.ts.snap (100%) rename src/plugins/{expressions/common/expression_functions/specs/tests => data/common/search/expressions}/kibana.test.ts (92%) rename src/plugins/{expressions/common/expression_functions/specs => data/common/search/expressions}/kibana.ts (82%) rename src/plugins/{expressions/common/expression_functions/specs => data/common/search/expressions}/kibana_context.ts (84%) rename src/plugins/{expressions/common/expression_types/specs/kibana_context.ts => data/common/search/expressions/kibana_context_type.ts} (79%) create mode 100644 src/plugins/data/common/search/expressions/utils/function_wrapper.ts rename src/plugins/{data/common/utils => kibana_utils/common}/abort_utils.test.ts (84%) rename src/plugins/{data/common/utils => kibana_utils/common}/abort_utils.ts (91%) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.executioncontextsearch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.executioncontextsearch.md new file mode 100644 index 0000000000000..67dcb2fa44241 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.executioncontextsearch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ExecutionContextSearch](./kibana-plugin-plugins-data-public.executioncontextsearch.md) + +## ExecutionContextSearch type + +Signature: + +```typescript +export declare type ExecutionContextSearch = { + filters?: Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +}; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibana.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibana.md new file mode 100644 index 0000000000000..c91f2e8144ead --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibana.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ExpressionFunctionKibana](./kibana-plugin-plugins-data-public.expressionfunctionkibana.md) + +## ExpressionFunctionKibana type + +Signature: + +```typescript +export declare type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext, ExecutionContext>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md new file mode 100644 index 0000000000000..97d2e81d45554 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ExpressionFunctionKibanaContext](./kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md) + +## ExpressionFunctionKibanaContext type + +Signature: + +```typescript +export declare type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments, Promise, ExecutionContext>; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md similarity index 54% rename from docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md index 6e38adde3ba91..4849d82b94a62 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionValueSearchContext](./kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ExpressionValueSearchContext](./kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md) ## ExpressionValueSearchContext type diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibanacontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kibanacontext.md similarity index 51% rename from docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibanacontext.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kibanacontext.md index 108533e8de357..cb8842c66761d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibanacontext.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kibanacontext.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [KibanaContext](./kibana-plugin-plugins-expressions-public.kibanacontext.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [KibanaContext](./kibana-plugin-plugins-data-public.kibanacontext.md) ## KibanaContext type diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 255a9947858f6..bafcd8bdffff9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -142,7 +142,11 @@ | [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md) | | | [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | | | [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md) | | +| [ExecutionContextSearch](./kibana-plugin-plugins-data-public.executioncontextsearch.md) | | | [ExistsFilter](./kibana-plugin-plugins-data-public.existsfilter.md) | | +| [ExpressionFunctionKibana](./kibana-plugin-plugins-data-public.expressionfunctionkibana.md) | | +| [ExpressionFunctionKibanaContext](./kibana-plugin-plugins-data-public.expressionfunctionkibanacontext.md) | | +| [ExpressionValueSearchContext](./kibana-plugin-plugins-data-public.expressionvaluesearchcontext.md) | | | [FieldFormatId](./kibana-plugin-plugins-data-public.fieldformatid.md) | id type is needed for creating custom converters. | | [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* | | [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md) | | @@ -161,6 +165,7 @@ | [InputTimeRange](./kibana-plugin-plugins-data-public.inputtimerange.md) | | | [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | | [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | search source interface | +| [KibanaContext](./kibana-plugin-plugins-data-public.kibanacontext.md) | | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index d1d20291a6799..bb45222d096a0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "timeHistory" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.executioncontextsearch.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.executioncontextsearch.md new file mode 100644 index 0000000000000..2f94dbe970d44 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.executioncontextsearch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ExecutionContextSearch](./kibana-plugin-plugins-data-server.executioncontextsearch.md) + +## ExecutionContextSearch type + +Signature: + +```typescript +export declare type ExecutionContextSearch = { + filters?: Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +}; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibana.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibana.md new file mode 100644 index 0000000000000..d80ff78dd803c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibana.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ExpressionFunctionKibana](./kibana-plugin-plugins-data-server.expressionfunctionkibana.md) + +## ExpressionFunctionKibana type + +Signature: + +```typescript +export declare type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext, ExecutionContext>; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md new file mode 100644 index 0000000000000..b67f7b8c4b60d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ExpressionFunctionKibanaContext](./kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md) + +## ExpressionFunctionKibanaContext type + +Signature: + +```typescript +export declare type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments, Promise, ExecutionContext>; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md similarity index 54% rename from docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md index bf64dfe4c86f7..8015482ddec1e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionValueSearchContext](./kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ExpressionValueSearchContext](./kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md) ## ExpressionValueSearchContext type diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibanacontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kibanacontext.md similarity index 51% rename from docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibanacontext.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kibanacontext.md index 023748173e7dd..1ddc43c633b26 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibanacontext.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kibanacontext.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [KibanaContext](./kibana-plugin-plugins-expressions-server.kibanacontext.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) ## KibanaContext type diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 82d0a5a3182b9..49cf12486cbb8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -88,6 +88,10 @@ | [AggGroupName](./kibana-plugin-plugins-data-server.agggroupname.md) | | | [AggParam](./kibana-plugin-plugins-data-server.aggparam.md) | | | [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-server.esaggsexpressionfunctiondefinition.md) | | +| [ExecutionContextSearch](./kibana-plugin-plugins-data-server.executioncontextsearch.md) | | +| [ExpressionFunctionKibana](./kibana-plugin-plugins-data-server.expressionfunctionkibana.md) | | +| [ExpressionFunctionKibanaContext](./kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md) | | +| [ExpressionValueSearchContext](./kibana-plugin-plugins-data-server.expressionvaluesearchcontext.md) | | | [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md) | | | [Filter](./kibana-plugin-plugins-data-server.filter.md) | | | [IAggConfig](./kibana-plugin-plugins-data-server.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | @@ -96,6 +100,7 @@ | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md) | | | [IMetricAggType](./kibana-plugin-plugins-data-server.imetricaggtype.md) | | +| [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | | [Query](./kibana-plugin-plugins-data-server.query.md) | | | [TabbedAggRow](./kibana-plugin-plugins-data-server.tabbedaggrow.md) | \* | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md index 139c5794f0146..43129891c5412 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md @@ -11,7 +11,7 @@ setup(core: CoreSetup, { expressio __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; }; }; ``` @@ -29,7 +29,7 @@ setup(core: CoreSetup, { expressio __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; }; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 03d3485fce9ee..555a5c2374da7 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md index 2a1a78b8fcb1a..86d24534f7a44 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md @@ -9,7 +9,7 @@ Signature: ```typescript -export interface ExecutionContext +export interface ExecutionContext ``` ## Properties diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md deleted file mode 100644 index abe8e0ae161ad..0000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [kibana](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md) - -## ExpressionFunctionDefinitions.kibana property - -Signature: - -```typescript -kibana: ExpressionFunctionKibana; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md deleted file mode 100644 index 4b58fd84e160d..0000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [kibana\_context](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md) - -## ExpressionFunctionDefinitions.kibana\_context property - -Signature: - -```typescript -kibana_context: ExpressionFunctionKibanaContext; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md index 53f090ea30c3f..c6e00842a31e6 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md @@ -20,8 +20,6 @@ export interface ExpressionFunctionDefinitions | [cumulative\_sum](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | | [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | -| [kibana\_context](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | -| [kibana](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | | [moving\_average](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage | | | [theme](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme | | | [var\_set](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md deleted file mode 100644 index 8ccf48ba28527..0000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionKibana](./kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md) - -## ExpressionFunctionKibana type - -Signature: - -```typescript -export declare type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext>; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index e2ad6215e25d0..2dfc67d2af5fa 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -21,7 +21,7 @@ export interface IExpressionLoaderParams | [disableCaching](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.disablecaching.md) | boolean | | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters | | | [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType | | -| [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | ExecutionContextSearch | | +| [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | | [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | unknown | | | [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | Record<string, any> | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md index 523d0c562f7ca..6b5fad950c4e9 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md @@ -7,5 +7,5 @@ Signature: ```typescript -searchContext?: ExecutionContextSearch; +searchContext?: SerializableState; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibana_context_name.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibana_context_name.md deleted file mode 100644 index e568db84f383d..0000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.kibana_context_name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [KIBANA\_CONTEXT\_NAME](./kibana-plugin-plugins-expressions-public.kibana_context_name.md) - -## KIBANA\_CONTEXT\_NAME type - -Signature: - -```typescript -export declare type KIBANA_CONTEXT_NAME = 'kibana_context'; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md index db09f966e2fa5..a03ea32482011 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md @@ -99,7 +99,6 @@ | [ExpressionAstExpression](./kibana-plugin-plugins-expressions-public.expressionastexpression.md) | | | [ExpressionAstFunction](./kibana-plugin-plugins-expressions-public.expressionastfunction.md) | | | [ExpressionAstNode](./kibana-plugin-plugins-expressions-public.expressionastnode.md) | | -| [ExpressionFunctionKibana](./kibana-plugin-plugins-expressions-public.expressionfunctionkibana.md) | | | [ExpressionRendererComponent](./kibana-plugin-plugins-expressions-public.expressionrenderercomponent.md) | | | [ExpressionsServiceSetup](./kibana-plugin-plugins-expressions-public.expressionsservicesetup.md) | The public contract that ExpressionsService provides to other plugins in Kibana Platform in \*setup\* life-cycle. | | [ExpressionsSetup](./kibana-plugin-plugins-expressions-public.expressionssetup.md) | Expressions public setup contract, extends [ExpressionsServiceSetup](./kibana-plugin-plugins-expressions-public.expressionsservicesetup.md) | @@ -110,13 +109,10 @@ | [ExpressionValueFilter](./kibana-plugin-plugins-expressions-public.expressionvaluefilter.md) | Represents an object that is a Filter. | | [ExpressionValueNum](./kibana-plugin-plugins-expressions-public.expressionvaluenum.md) | | | [ExpressionValueRender](./kibana-plugin-plugins-expressions-public.expressionvaluerender.md) | Represents an object that is intended to be rendered. | -| [ExpressionValueSearchContext](./kibana-plugin-plugins-expressions-public.expressionvaluesearchcontext.md) | | | [ExpressionValueUnboxed](./kibana-plugin-plugins-expressions-public.expressionvalueunboxed.md) | | | [FontLabel](./kibana-plugin-plugins-expressions-public.fontlabel.md) | This type contains a unions of all supported font labels, or the the name of the font the user would see in a UI. | | [FontValue](./kibana-plugin-plugins-expressions-public.fontvalue.md) | This type contains a union of all supported font values, equivalent to the CSS font-value property. | | [InterpreterErrorType](./kibana-plugin-plugins-expressions-public.interpretererrortype.md) | | -| [KIBANA\_CONTEXT\_NAME](./kibana-plugin-plugins-expressions-public.kibana_context_name.md) | | -| [KibanaContext](./kibana-plugin-plugins-expressions-public.kibanacontext.md) | | | [KnownTypeToString](./kibana-plugin-plugins-expressions-public.knowntypetostring.md) | Map the type of the generic to a string-based representation of the type.If the provided generic is its own type interface, we use the value of the type key as a string literal type for it. | | [PointSeries](./kibana-plugin-plugins-expressions-public.pointseries.md) | A PointSeries is a unique structure that represents dots on a chart. | | [PointSeriesColumnName](./kibana-plugin-plugins-expressions-public.pointseriescolumnname.md) | Allowed column names in a PointSeries | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md index 047879fd99255..e2547cc9470d1 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md @@ -9,7 +9,7 @@ Signature: ```typescript -export interface ExecutionContext +export interface ExecutionContext ``` ## Properties diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md deleted file mode 100644 index 8e6d189f8f450..0000000000000 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [kibana](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md) - -## ExpressionFunctionDefinitions.kibana property - -Signature: - -```typescript -kibana: ExpressionFunctionKibana; -``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md deleted file mode 100644 index f9e248ad6d913..0000000000000 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [kibana\_context](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md) - -## ExpressionFunctionDefinitions.kibana\_context property - -Signature: - -```typescript -kibana_context: ExpressionFunctionKibanaContext; -``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md index 6f152bb10b37e..219678244951b 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md @@ -20,8 +20,6 @@ export interface ExpressionFunctionDefinitions | [cumulative\_sum](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | | [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | -| [kibana\_context](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | -| [kibana](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | | [moving\_average](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage | | | [theme](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme | | | [var\_set](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md deleted file mode 100644 index aac2ae1c3ca4e..0000000000000 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionKibana](./kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md) - -## ExpressionFunctionKibana type - -Signature: - -```typescript -export declare type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext>; -``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibana_context_name.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibana_context_name.md deleted file mode 100644 index bd47c52e0d5ce..0000000000000 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.kibana_context_name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [KIBANA\_CONTEXT\_NAME](./kibana-plugin-plugins-expressions-server.kibana_context_name.md) - -## KIBANA\_CONTEXT\_NAME type - -Signature: - -```typescript -export declare type KIBANA_CONTEXT_NAME = 'kibana_context'; -``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.md index 9e2189dad2732..5f7f373cd927f 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.md @@ -83,7 +83,6 @@ | [ExpressionAstExpression](./kibana-plugin-plugins-expressions-server.expressionastexpression.md) | | | [ExpressionAstFunction](./kibana-plugin-plugins-expressions-server.expressionastfunction.md) | | | [ExpressionAstNode](./kibana-plugin-plugins-expressions-server.expressionastnode.md) | | -| [ExpressionFunctionKibana](./kibana-plugin-plugins-expressions-server.expressionfunctionkibana.md) | | | [ExpressionsServerSetup](./kibana-plugin-plugins-expressions-server.expressionsserversetup.md) | | | [ExpressionsServerStart](./kibana-plugin-plugins-expressions-server.expressionsserverstart.md) | | | [ExpressionValue](./kibana-plugin-plugins-expressions-server.expressionvalue.md) | | @@ -93,13 +92,10 @@ | [ExpressionValueFilter](./kibana-plugin-plugins-expressions-server.expressionvaluefilter.md) | Represents an object that is a Filter. | | [ExpressionValueNum](./kibana-plugin-plugins-expressions-server.expressionvaluenum.md) | | | [ExpressionValueRender](./kibana-plugin-plugins-expressions-server.expressionvaluerender.md) | Represents an object that is intended to be rendered. | -| [ExpressionValueSearchContext](./kibana-plugin-plugins-expressions-server.expressionvaluesearchcontext.md) | | | [ExpressionValueUnboxed](./kibana-plugin-plugins-expressions-server.expressionvalueunboxed.md) | | | [FontLabel](./kibana-plugin-plugins-expressions-server.fontlabel.md) | This type contains a unions of all supported font labels, or the the name of the font the user would see in a UI. | | [FontValue](./kibana-plugin-plugins-expressions-server.fontvalue.md) | This type contains a union of all supported font values, equivalent to the CSS font-value property. | | [InterpreterErrorType](./kibana-plugin-plugins-expressions-server.interpretererrortype.md) | | -| [KIBANA\_CONTEXT\_NAME](./kibana-plugin-plugins-expressions-server.kibana_context_name.md) | | -| [KibanaContext](./kibana-plugin-plugins-expressions-server.kibanacontext.md) | | | [KnownTypeToString](./kibana-plugin-plugins-expressions-server.knowntypetostring.md) | Map the type of the generic to a string-based representation of the type.If the provided generic is its own type interface, we use the value of the type key as a string literal type for it. | | [PointSeries](./kibana-plugin-plugins-expressions-server.pointseries.md) | A PointSeries is a unique structure that represents dots on a chart. | | [PointSeriesColumnName](./kibana-plugin-plugins-expressions-server.pointseriescolumnname.md) | Allowed column names in a PointSeries | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md index ba9060e01e57d..5a1ab83551d34 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md @@ -11,5 +11,5 @@ Signature: ```typescript -readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; +readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md index 3e433809f9471..5b0b3eea01cb1 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; +readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md index 83afcab29689d..2dda422046318 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerActions: (triggerId: T) => Action[]; +readonly getTriggerActions: (triggerId: T) => Action[]; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md index 879f5a3d8628a..e087753726a8a 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; +readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md index 7fade7c4c841b..f9eb693b492f7 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md @@ -21,19 +21,19 @@ export declare class UiActionsService | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry | | -| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | +| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | | [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | | [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void | Removes all registered triggers and actions. | | [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void | | | [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | | [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService | | | [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService | "Fork" a separate instance of UiActionsService that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService are only available within this instance. | -| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | +| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION"> | | | [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | -| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[] | | -| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[]> | | +| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">[] | | +| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">[]> | | | [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean | | -| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | +| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION"> | | | [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void | | | [triggers](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggers.md) | | TriggerRegistry | | | [triggerToActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggertoactions.md) | | TriggerToActionsRegistry | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md index eeda7b503037d..bd340eb76fbac 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; +readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">; ``` diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/__snapshots__/kibana.test.ts.snap b/src/plugins/data/common/search/expressions/__snapshots__/kibana.test.ts.snap similarity index 100% rename from src/plugins/expressions/common/expression_functions/specs/tests/__snapshots__/kibana.test.ts.snap rename to src/plugins/data/common/search/expressions/__snapshots__/kibana.test.ts.snap diff --git a/src/plugins/data/common/search/expressions/esaggs.ts b/src/plugins/data/common/search/expressions/esaggs.ts index 4f65babdcd360..47d97a81a67b1 100644 --- a/src/plugins/data/common/search/expressions/esaggs.ts +++ b/src/plugins/data/common/search/expressions/esaggs.ts @@ -17,11 +17,8 @@ * under the License. */ -import { - KibanaContext, - Datatable, - ExpressionFunctionDefinition, -} from '../../../../../plugins/expressions/common'; +import { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { KibanaContext } from './kibana_context_type'; type Input = KibanaContext | null; type Output = Promise; diff --git a/src/plugins/data/common/search/expressions/index.ts b/src/plugins/data/common/search/expressions/index.ts index 25839a805d8c5..28d892d091956 100644 --- a/src/plugins/data/common/search/expressions/index.ts +++ b/src/plugins/data/common/search/expressions/index.ts @@ -17,5 +17,8 @@ * under the License. */ +export * from './kibana'; +export * from './kibana_context'; +export * from './kibana_context_type'; export * from './esaggs'; export * from './utils'; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts b/src/plugins/data/common/search/expressions/kibana.test.ts similarity index 92% rename from src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts rename to src/plugins/data/common/search/expressions/kibana.test.ts index e5c4b92de4fdb..58fee1ee1f6a6 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts +++ b/src/plugins/data/common/search/expressions/kibana.test.ts @@ -17,14 +17,14 @@ * under the License. */ +import { ExecutionContext } from 'src/plugins/expressions/common'; +import { kibana } from './kibana'; +import { ExpressionValueSearchContext } from './kibana_context_type'; import { functionWrapper } from './utils'; -import { kibana } from '../kibana'; -import { ExecutionContext } from '../../../execution/types'; -import { KibanaContext, ExpressionValueSearchContext } from '../../../expression_types'; describe('interpreter/functions#kibana', () => { const fn = functionWrapper(kibana); - let input: Partial; + let input: Partial; let search: ExpressionValueSearchContext; let context: ExecutionContext; diff --git a/src/plugins/expressions/common/expression_functions/specs/kibana.ts b/src/plugins/data/common/search/expressions/kibana.ts similarity index 82% rename from src/plugins/expressions/common/expression_functions/specs/kibana.ts rename to src/plugins/data/common/search/expressions/kibana.ts index 3ec4c23eab28d..c31219284760a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/kibana.ts +++ b/src/plugins/data/common/search/expressions/kibana.ts @@ -18,8 +18,9 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition } from '../types'; -import { ExpressionValueSearchContext } from '../../expression_types'; +import { ExecutionContext, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { Adapters } from 'src/plugins/inspector/common'; +import { ExpressionValueSearchContext, ExecutionContextSearch } from './kibana_context_type'; const toArray = (query: undefined | T | T[]): T[] => !query ? [] : Array.isArray(query) ? query : [query]; @@ -29,7 +30,8 @@ export type ExpressionFunctionKibana = ExpressionFunctionDefinition< // TODO: Get rid of the `null` type below. ExpressionValueSearchContext | null, object, - ExpressionValueSearchContext + ExpressionValueSearchContext, + ExecutionContext >; export const kibana: ExpressionFunctionKibana = { @@ -38,7 +40,7 @@ export const kibana: ExpressionFunctionKibana = { inputTypes: ['kibana_context', 'null'], - help: i18n.translate('expressions.functions.kibana.help', { + help: i18n.translate('data.search.functions.kibana.help', { defaultMessage: 'Gets kibana global context', }), diff --git a/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts similarity index 84% rename from src/plugins/expressions/common/expression_functions/specs/kibana_context.ts rename to src/plugins/data/common/search/expressions/kibana_context.ts index 2b7d1b8ed9d76..7bf14ff02316e 100644 --- a/src/plugins/expressions/common/expression_functions/specs/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -16,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import { uniqBy } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition } from '../../expression_functions'; -import { KibanaContext } from '../../expression_types'; -import { Query, uniqFilters } from '../../../../data/common'; +import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expressions/common'; +import { Adapters } from 'src/plugins/inspector/common'; +import { Query, uniqFilters } from '../../query'; +import { ExecutionContextSearch, KibanaContext } from './kibana_context_type'; interface Arguments { q?: string | null; @@ -33,7 +35,8 @@ export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition< 'kibana_context', KibanaContext | null, Arguments, - Promise + Promise, + ExecutionContext >; const getParsedValue = (data: any, defaultValue: any) => @@ -49,7 +52,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { name: 'kibana_context', type: 'kibana_context', inputTypes: ['kibana_context', 'null'], - help: i18n.translate('expressions.functions.kibana_context.help', { + help: i18n.translate('data.search.functions.kibana_context.help', { defaultMessage: 'Updates kibana global context', }), args: { @@ -57,28 +60,28 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = { types: ['string', 'null'], aliases: ['query', '_'], default: null, - help: i18n.translate('expressions.functions.kibana_context.q.help', { + help: i18n.translate('data.search.functions.kibana_context.q.help', { defaultMessage: 'Specify Kibana free form text query', }), }, filters: { types: ['string', 'null'], default: '"[]"', - help: i18n.translate('expressions.functions.kibana_context.filters.help', { + help: i18n.translate('data.search.functions.kibana_context.filters.help', { defaultMessage: 'Specify Kibana generic filters', }), }, timeRange: { types: ['string', 'null'], default: null, - help: i18n.translate('expressions.functions.kibana_context.timeRange.help', { + help: i18n.translate('data.search.functions.kibana_context.timeRange.help', { defaultMessage: 'Specify Kibana time range filter', }), }, savedSearchId: { types: ['string', 'null'], default: null, - help: i18n.translate('expressions.functions.kibana_context.savedSearchId.help', { + help: i18n.translate('data.search.functions.kibana_context.savedSearchId.help', { defaultMessage: 'Specify saved search ID to be used for queries and filters', }), }, diff --git a/src/plugins/expressions/common/expression_types/specs/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context_type.ts similarity index 79% rename from src/plugins/expressions/common/expression_types/specs/kibana_context.ts rename to src/plugins/data/common/search/expressions/kibana_context_type.ts index 3af7b990429c0..a5b459a607991 100644 --- a/src/plugins/expressions/common/expression_types/specs/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context_type.ts @@ -17,8 +17,16 @@ * under the License. */ -import { ExpressionValueBoxed } from '../types'; -import { ExecutionContextSearch } from '../../execution/types'; +import { ExpressionValueBoxed } from 'src/plugins/expressions/common'; +import { Filter } from '../../es_query'; +import { Query, TimeRange } from '../../query'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type ExecutionContextSearch = { + filters?: Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +}; export type ExpressionValueSearchContext = ExpressionValueBoxed< 'kibana_context', diff --git a/src/plugins/data/common/search/expressions/utils/function_wrapper.ts b/src/plugins/data/common/search/expressions/utils/function_wrapper.ts new file mode 100644 index 0000000000000..b5e45d9ca1de0 --- /dev/null +++ b/src/plugins/data/common/search/expressions/utils/function_wrapper.ts @@ -0,0 +1,34 @@ +/* + * 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 { mapValues } from 'lodash'; +import { AnyExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expressions/common'; + +/** + * Takes a function spec and passes in default args, + * overriding with any provided args. + */ +export const functionWrapper = (spec: AnyExpressionFunctionDefinition) => { + const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); + return ( + context: object | null, + args: Record = {}, + handlers: ExecutionContext = {} as ExecutionContext + ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); +}; diff --git a/src/plugins/data/common/search/expressions/utils/index.ts b/src/plugins/data/common/search/expressions/utils/index.ts index 75c1809770c78..39f88b0f13fee 100644 --- a/src/plugins/data/common/search/expressions/utils/index.ts +++ b/src/plugins/data/common/search/expressions/utils/index.ts @@ -18,3 +18,4 @@ */ export * from './courier_inspector_stats'; +export * from './function_wrapper'; diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 33989f3ad50a7..8b8686c51b9c1 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -19,4 +19,3 @@ /** @internal */ export { shortenDottedString } from './shorten_dotted_string'; -export { AbortError, toPromise, getCombinedSignal } from './abort_utils'; diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index 9cb9b1745373a..d6f2534bd5e3b 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -8,7 +8,7 @@ "uiActions" ], "optionalPlugins": ["usageCollection"], - "extraPublicDirs": ["common", "common/utils/abort_utils"], + "extraPublicDirs": ["common"], "requiredBundles": [ "usageCollection", "kibanaUtils", diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ce020a9742399..129addf3de70e 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -338,6 +338,12 @@ export { OptionedParamType, OptionedValueProp, ParsedInterval, + // expressions + ExecutionContextSearch, + ExpressionFunctionKibana, + ExpressionFunctionKibanaContext, + ExpressionValueSearchContext, + KibanaContext, // tabify TabbedAggColumn, TabbedAggRow, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index d52edbe5b11dd..bc2bb2663f163 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -6,6 +6,7 @@ import { $Values } from '@kbn/utility-types'; import { Action } from 'history'; +import { Adapters as Adapters_2 } from 'src/plugins/inspector/common'; import { ApiResponse } from '@elastic/elasticsearch'; import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transport'; import { ApplicationStart } from 'kibana/public'; @@ -16,6 +17,7 @@ import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; +import { Datatable as Datatable_2 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; @@ -26,8 +28,12 @@ import { EuiComboBoxProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; +import { ExecutionContext } from 'src/plugins/expressions/common'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition as ExpressionFunctionDefinition_2 } from 'src/plugins/expressions/public'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; +import { ExpressionValueBoxed } from 'src/plugins/expressions/common'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; @@ -65,7 +71,7 @@ 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 { SavedObjectReference as SavedObjectReference_2 } from 'src/core/types'; +import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; @@ -80,7 +86,6 @@ import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; -import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_GLOBAL_APPLY_FILTER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -525,7 +530,6 @@ export enum ES_FIELD_TYPES { // @public (undocumented) export const ES_SEARCH_STRATEGY = "es"; -// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDefinition" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Output" needs to be exported by the entry point index.d.ts @@ -541,7 +545,7 @@ export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'e // Warning: (ae-missing-release-tag) "EsdslExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition; +export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2; // Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -647,6 +651,15 @@ export type EsQuerySortValue = Record; +// Warning: (ae-missing-release-tag) "ExecutionContextSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExecutionContextSearch = { + filters?: Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +}; + // Warning: (ae-missing-release-tag) "ExistsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -655,12 +668,28 @@ export type ExistsFilter = Filter & { exists?: FilterExistsProperty; }; +// Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext, ExecutionContext>; + +// Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_2, Promise, ExecutionContext>; + +// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionValueSearchContext = ExpressionValueBoxed<'kibana_context', ExecutionContextSearch>; + // Warning: (ae-missing-release-tag) "extractReferences" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const extractSearchSourceReferences: (state: SearchSourceFields) => [SearchSourceFields & { indexRefName?: string; -}, SavedObjectReference_2[]]; +}, SavedObjectReference[]]; // Warning: (ae-missing-release-tag) "FieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1368,7 +1397,7 @@ export interface IndexPatternTypeMeta { // @public (undocumented) export const injectSearchSourceReferences: (searchSourceFields: SearchSourceFields & { indexRefName: string; -}, references: SavedObjectReference_2[]) => SearchSourceFields; +}, references: SavedObjectReference[]) => SearchSourceFields; // Warning: (ae-missing-release-tag) "InputTimeRange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1510,6 +1539,11 @@ export enum KBN_FIELD_TYPES { UNKNOWN = "unknown" } +// Warning: (ae-missing-release-tag) "KibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type KibanaContext = ExpressionValueSearchContext; + // Warning: (ae-missing-release-tag) "KueryNode" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1962,8 +1996,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "timeHistory" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "screenTitle" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -2321,21 +2355,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:393:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:393:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:393:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:393:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/errors/timeout_error.test.tsx b/src/plugins/data/public/search/errors/timeout_error.test.tsx index ad3384c389fbf..2547ee41cbbd7 100644 --- a/src/plugins/data/public/search/errors/timeout_error.test.tsx +++ b/src/plugins/data/public/search/errors/timeout_error.test.tsx @@ -23,7 +23,7 @@ import { coreMock } from '../../../../../core/public/mocks'; const startMock = coreMock.createStart(); import { mount } from 'enzyme'; -import { AbortError } from 'src/plugins/data/common'; +import { AbortError } from '../../../../kibana_utils/public'; describe('SearchTimeoutError', () => { beforeEach(() => { diff --git a/src/plugins/data/public/search/expressions/esdsl.ts b/src/plugins/data/public/search/expressions/esdsl.ts index 2efb21671b5b7..07e904928744f 100644 --- a/src/plugins/data/public/search/expressions/esdsl.ts +++ b/src/plugins/data/public/search/expressions/esdsl.ts @@ -18,15 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { - KibanaContext, - ExpressionFunctionDefinition, -} from '../../../../../plugins/expressions/public'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; import { getSearchService, getUiSettings } from '../../services'; import { EsRawResponse } from './es_raw_response'; import { RequestStatistics, RequestAdapter } from '../../../../inspector/common'; -import { IEsSearchResponse } from '../../../common/search/es_search'; +import { IEsSearchResponse, KibanaContext } from '../../../common/search'; import { buildEsQuery, getEsQueryConfig } from '../../../common/es_query/es_query'; import { DataPublicPluginStart } from '../../types'; diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index 472caa5e4f45f..60274261da25f 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -21,7 +21,7 @@ import { CoreSetup, CoreStart } from '../../../../core/public'; import { coreMock } from '../../../../core/public/mocks'; import { IEsSearchRequest } from '../../common/search'; import { SearchInterceptor } from './search_interceptor'; -import { AbortError } from '../../common'; +import { AbortError } from '../../../kibana_utils/public'; import { SearchTimeoutError, PainlessError, TimeoutErrorMode } from './errors'; import { searchServiceMock } from './mocks'; import { ISearchStart } from '.'; diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 3584d75ab86bb..78e65802bcf99 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -24,13 +24,11 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { - AbortError, IKibanaSearchRequest, IKibanaSearchResponse, ISearchOptions, ES_SEARCH_STRATEGY, ISessionService, - getCombinedSignal, } from '../../common'; import { SearchUsageCollector } from './collectors'; import { @@ -43,6 +41,7 @@ import { getHttpError, } from './errors'; import { toMountPoint } from '../../../kibana_react/public'; +import { AbortError, getCombinedAbortSignal } from '../../../kibana_utils/public'; export interface SearchInterceptorDeps { http: CoreSetup['http']; @@ -170,7 +169,9 @@ export class SearchInterceptor { ...(abortSignal ? [abortSignal] : []), ]; - const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = getCombinedSignal(signals); + const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = getCombinedAbortSignal( + signals + ); const cleanup = () => { subscription.unsubscribe(); combinedSignal.removeEventListener('abort', cleanup); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index e5a50077518af..96fb3f91ea85f 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -23,10 +23,13 @@ import { ISearchSetup, ISearchStart, SearchEnhancements } from './types'; import { handleResponse } from './fetch'; import { + kibana, + kibanaContext, + kibanaContextFunction, ISearchGeneric, - SearchSourceService, - SearchSourceDependencies, ISessionService, + SearchSourceDependencies, + SearchSourceService, } from '../../common/search'; import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; @@ -85,6 +88,10 @@ export class SearchService implements Plugin { session: this.sessionService, }); + expressions.registerFunction(kibana); + expressions.registerFunction(kibanaContextFunction); + expressions.registerType(kibanaContext); + expressions.registerFunction(esdsl); expressions.registerType(esRawResponse); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 9a9b8b67730cc..0e9736c9e256a 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -206,6 +206,12 @@ export { OptionedParamType, OptionedValueProp, ParsedInterval, + // expressions + ExecutionContextSearch, + ExpressionFunctionKibana, + ExpressionFunctionKibanaContext, + ExpressionValueSearchContext, + KibanaContext, // search ISearchOptions, IEsSearchRequest, diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 88f24b7ca5a70..3ec4e7e64e382 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -96,7 +96,7 @@ export class DataServerPlugin core.uiSettings.register(getUiSettings()); const searchSetup = this.searchService.setup(core, { - registerFunction: expressions.registerFunction, + expressions, usageCollection, }); diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 2b513be147e9d..0700afd8d6c83 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -44,7 +44,10 @@ describe('Search service', () => { it('exposes proper contract', async () => { const setup = plugin.setup(mockCoreSetup, ({ packageInfo: { version: '8' }, - registerFunction: jest.fn(), + expressions: { + registerFunction: jest.fn(), + registerType: jest.fn(), + }, } as unknown) as SearchServiceSetupDependencies); expect(setup).toHaveProperty('aggs'); expect(setup).toHaveProperty('registerSearchStrategy'); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index c500c62914c0b..c5a60c42a41c9 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -30,6 +30,7 @@ import { StartServicesAccessor, } from 'src/core/server'; import { first } from 'rxjs/operators'; +import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { ISearchSetup, ISearchStart, @@ -38,7 +39,7 @@ import { SearchStrategyDependencies, } from './types'; -import { AggsService, AggsSetupDependencies } from './aggs'; +import { AggsService } from './aggs'; import { FieldFormatsStart } from '../field_formats'; import { IndexPatternsServiceStart } from '../index_patterns'; @@ -50,15 +51,18 @@ import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; import { - IKibanaSearchRequest, - IKibanaSearchResponse, IEsSearchRequest, IEsSearchResponse, + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchClient, + ISearchOptions, + kibana, + kibanaContext, + kibanaContextFunction, SearchSourceDependencies, - SearchSourceService, searchSourceRequiredUiSettings, - ISearchOptions, - ISearchClient, + SearchSourceService, } from '../../common/search'; import { getShardDelayBucketAgg, @@ -77,7 +81,7 @@ type StrategyMap = Record>; /** @internal */ export interface SearchServiceSetupDependencies { - registerFunction: AggsSetupDependencies['registerFunction']; + expressions: ExpressionsServerSetup; usageCollection?: UsageCollectionSetup; } @@ -106,7 +110,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup<{}, DataPluginStart>, - { registerFunction, usageCollection }: SearchServiceSetupDependencies + { expressions, usageCollection }: SearchServiceSetupDependencies ): ISearchSetup { const usage = usageCollection ? usageProvider(core) : undefined; @@ -137,7 +141,11 @@ export class SearchService implements Plugin { registerUsageCollector(usageCollection, this.initializerContext); } - const aggs = this.aggsService.setup({ registerFunction }); + expressions.registerFunction(kibana); + expressions.registerFunction(kibanaContextFunction); + expressions.registerType(kibanaContext); + + const aggs = this.aggsService.setup({ registerFunction: expressions.registerFunction }); this.initializerContext.config .create() @@ -146,7 +154,7 @@ export class SearchService implements Plugin { .then((value) => { if (value.search.aggs.shardDelay.enabled) { aggs.types.registerBucket(SHARD_DELAY_AGG_NAME, getShardDelayBucketAgg); - registerFunction(aggShardDelay); + expressions.registerFunction(aggShardDelay); } }); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index f62a70c9e4ce1..5b0e0499e29bf 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -5,6 +5,7 @@ ```ts import { $Values } from '@kbn/utility-types'; +import { Adapters } from 'src/plugins/inspector/common'; import { ApiResponse } from '@elastic/elasticsearch'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; @@ -13,14 +14,18 @@ import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; import { CoreStart } from 'src/core/server'; import { CoreStart as CoreStart_2 } from 'kibana/server'; +import { Datatable } from 'src/plugins/expressions/common'; import { DatatableColumn } from 'src/plugins/expressions'; import { Duration } from 'moment'; import { ElasticsearchClient } from 'kibana/server'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; +import { ExecutionContext } from 'src/plugins/expressions/common'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; +import { ExpressionValueBoxed } from 'src/plugins/expressions/common'; import { ISavedObjectsRepository } from 'kibana/server'; import { IScopedClusterClient } from 'src/core/server'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; @@ -55,7 +60,6 @@ import { ToastInputFields } from 'src/core/public/notifications'; import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { Unit } from '@elastic/datemath'; -import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; // Warning: (ae-forgotten-export) The symbol "AggConfigSerialized" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -224,7 +228,6 @@ export enum ES_FIELD_TYPES { // @public (undocumented) export const ES_SEARCH_STRATEGY = "es"; -// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDefinition" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Output" needs to be exported by the entry point index.d.ts @@ -285,6 +288,31 @@ export interface EsQueryConfig { queryStringOptions: Record; } +// Warning: (ae-missing-release-tag) "ExecutionContextSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExecutionContextSearch = { + filters?: Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +}; + +// Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext, ExecutionContext>; + +// Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_2, Promise, ExecutionContext>; + +// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ExpressionValueSearchContext = ExpressionValueBoxed<'kibana_context', ExecutionContextSearch>; + // Warning: (ae-missing-release-tag) "FieldDescriptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -779,6 +807,11 @@ export enum KBN_FIELD_TYPES { UNKNOWN = "unknown" } +// Warning: (ae-missing-release-tag) "KibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type KibanaContext = ExpressionValueSearchContext; + // Warning: (ae-missing-release-tag) "KueryNode" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -885,7 +918,7 @@ export class Plugin implements Plugin_2 void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; }; }; // (undocumented) @@ -894,7 +927,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }; @@ -1162,21 +1195,21 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:250:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:272:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:276:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:279:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:256:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:257:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:272:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:273:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:277:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:278:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:282:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:285:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // src/plugins/data/server/search/types.ts:104:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index ba115a7538604..50a469c338d73 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -22,8 +22,7 @@ import { keys, last, mapValues, reduce, zipObject } from 'lodash'; import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; -import { Defer, now } from '../../../kibana_utils/common'; -import { toPromise } from '../../../data/common/utils/abort_utils'; +import { abortSignalToPromise, Defer, now } from '../../../kibana_utils/common'; import { RequestAdapter, DataAdapter, Adapters } from '../../../inspector/common'; import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error'; import { @@ -93,7 +92,7 @@ export class Execution< /** * Promise that rejects if/when abort controller sends "abort" signal. */ - private readonly abortRejection = toPromise(this.abortController.signal); + private readonly abortRejection = abortSignalToPromise(this.abortController.signal); /** * Races a given promise against the "abort" event of `abortController`. diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index 50475c3bd94ae..abe3e08fc20c2 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -17,16 +17,18 @@ * under the License. */ -import { ExpressionType } from '../expression_types'; +import { ExpressionType, SerializableState } from '../expression_types'; import { Adapters, DataAdapter, RequestAdapter } from '../../../inspector/common'; -import { TimeRange, Query, Filter } from '../../../data/common'; import { SavedObject, SavedObjectAttributes } from '../../../../core/public'; /** * `ExecutionContext` is an object available to all functions during a single execution; * it provides various methods to perform side-effects. */ -export interface ExecutionContext { +export interface ExecutionContext< + InspectorAdapters extends Adapters = Adapters, + ExecutionContextSearch extends SerializableState = SerializableState +> { /** * Get search context of the expression. */ @@ -79,9 +81,3 @@ export interface DefaultInspectorAdapters extends Adapters { requests: RequestAdapter; data: DataAdapter; } - -export interface ExecutionContextSearch { - filters?: Filter[]; - query?: Query | Query[]; - timeRange?: TimeRange; -} diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index 11706f65dbd32..10ccd014fd7bd 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -19,8 +19,6 @@ import { clog } from './clog'; import { font } from './font'; -import { kibana } from './kibana'; -import { kibanaContextFunction } from './kibana_context'; import { variableSet } from './var_set'; import { variable } from './var'; import { AnyExpressionFunctionDefinition } from '../types'; @@ -32,8 +30,6 @@ import { movingAverage } from './moving_average'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, font, - kibana, - kibanaContextFunction, variableSet, variable, theme, @@ -44,8 +40,6 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ export * from './clog'; export * from './font'; -export * from './kibana'; -export * from './kibana_context'; export * from './var_set'; export * from './var'; export * from './theme'; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts index 762f34e3f5566..efcb5555cc63b 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts @@ -20,12 +20,11 @@ import { functionWrapper } from './utils'; import { variable } from '../var'; import { ExecutionContext } from '../../../execution/types'; -import { KibanaContext } from '../../../expression_types'; describe('expression_functions', () => { describe('var', () => { const fn = functionWrapper(variable); - let input: Partial; + let input: Partial>; let context: ExecutionContext; beforeEach(() => { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts index 365ae5b89baea..17d9c4ab98c1a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts @@ -20,12 +20,11 @@ import { functionWrapper } from './utils'; import { variableSet } from '../var_set'; import { ExecutionContext } from '../../../execution/types'; -import { KibanaContext } from '../../../expression_types'; describe('expression_functions', () => { describe('var_set', () => { const fn = functionWrapper(variableSet); - let input: Partial; + let input: Partial>; let context: ExecutionContext; let variables: Record; diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index 4a93cfa9211ff..52e4b4d9facf3 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -24,8 +24,6 @@ import { ExecutionContext } from '../execution/types'; import { ExpressionFunctionClog, ExpressionFunctionFont, - ExpressionFunctionKibanaContext, - ExpressionFunctionKibana, ExpressionFunctionVarSet, ExpressionFunctionVar, ExpressionFunctionTheme, @@ -129,8 +127,6 @@ export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition< export interface ExpressionFunctionDefinitions { clog: ExpressionFunctionClog; font: ExpressionFunctionFont; - kibana_context: ExpressionFunctionKibanaContext; - kibana: ExpressionFunctionKibana; var_set: ExpressionFunctionVarSet; var: ExpressionFunctionVar; theme: ExpressionFunctionTheme; diff --git a/src/plugins/expressions/common/expression_types/specs/index.ts b/src/plugins/expressions/common/expression_types/specs/index.ts index 00c52a2545cd6..ec8dee1f7fc52 100644 --- a/src/plugins/expressions/common/expression_types/specs/index.ts +++ b/src/plugins/expressions/common/expression_types/specs/index.ts @@ -22,7 +22,6 @@ import { datatable } from './datatable'; import { error } from './error'; import { filter } from './filter'; import { image } from './image'; -import { kibanaContext } from './kibana_context'; import { nullType } from './null'; import { num } from './num'; import { number } from './number'; @@ -40,7 +39,6 @@ export const typeSpecs: AnyExpressionTypeDefinition[] = [ error, filter, image, - kibanaContext, nullType, num, number, @@ -57,7 +55,6 @@ export * from './datatable'; export * from './error'; export * from './filter'; export * from './image'; -export * from './kibana_context'; export * from './null'; export * from './num'; export * from './number'; diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index 01289ca1ae57a..c9cc0680360bb 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -26,7 +26,6 @@ import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; import { PersistableStateService, SerializableState } from '../../../kibana_utils/common'; import { Adapters } from '../../../inspector/common/adapters'; -import { ExecutionContextSearch } from '../execution'; /** * The public contract that `ExpressionsService` provides to other plugins @@ -48,7 +47,7 @@ export type ExpressionsServiceSetup = Pick< >; export interface ExpressionExecutionParams { - searchContext?: ExecutionContextSearch; + searchContext?: SerializableState; variables?: Record; diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index 67bbf4b6e5454..23c7fe722fdb3 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -6,7 +6,6 @@ "extraPublicDirs": ["common", "common/fonts"], "requiredBundles": [ "kibanaUtils", - "inspector", - "data" + "inspector" ] } diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 893d68238747d..385055bc2fdc2 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -66,7 +66,6 @@ export { ExpressionFunction, ExpressionFunctionDefinition, ExpressionFunctionDefinitions, - ExpressionFunctionKibana, ExpressionFunctionParameter, ExpressionImage, ExpressionRenderDefinition, @@ -81,7 +80,6 @@ export { ExpressionValueError, ExpressionValueNum, ExpressionValueRender, - ExpressionValueSearchContext, ExpressionValueUnboxed, ExpressionValueFilter, Font, @@ -96,8 +94,6 @@ export { InterpreterErrorType, IRegistry, isExpressionAstBuilder, - KIBANA_CONTEXT_NAME, - KibanaContext, KnownTypeToString, Overflow, parse, diff --git a/src/plugins/expressions/public/plugin.test.ts b/src/plugins/expressions/public/plugin.test.ts index d9dde1f6def68..0b21fe354d3d3 100644 --- a/src/plugins/expressions/public/plugin.test.ts +++ b/src/plugins/expressions/public/plugin.test.ts @@ -50,14 +50,6 @@ describe('ExpressionsPublicPlugin', () => { const bar = await setup.run('var_set name="foo" value="bar" | var name="foo"', null); expect(bar).toBe('bar'); }); - - test('kibana_context function is available', async () => { - const { setup } = await expressionsPluginMock.createPlugin(); - const result = await setup.run('kibana_context', null); - expect(result).toMatchObject({ - type: 'kibana_context', - }); - }); }); }); @@ -81,15 +73,6 @@ describe('ExpressionsPublicPlugin', () => { } `); }); - - test('"kibana" function return value of type "kibana_context"', async () => { - const { doStart } = await expressionsPluginMock.createPlugin(); - const start = await doStart(); - const execution = start.execute('kibana', null); - const result = await execution.getData(); - - expect((result as any).type).toBe('kibana_context'); - }); }); }); }); diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 773d61ebe2e28..17f8e6255f6bb 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -130,15 +130,15 @@ export class Execution = StateContainer, ExecutionPureTransitions>; +// Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ExecutionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ExecutionContext { +export interface ExecutionContext { abortSignal: AbortSignal; // Warning: (ae-forgotten-export) The symbol "SavedObjectAttributes" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SavedObject" needs to be exported by the entry point index.d.ts getSavedObject?: (type: string, id: string) => Promise>; - // Warning: (ae-forgotten-export) The symbol "ExecutionContextSearch" needs to be exported by the entry point index.d.ts getSearchContext: () => ExecutionContextSearch; getSearchSessionId: () => string | undefined; inspectorAdapters: InspectorAdapters; @@ -396,12 +396,6 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) font: ExpressionFunctionFont; - // (undocumented) - kibana: ExpressionFunctionKibana; - // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionKibanaContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - kibana_context: ExpressionFunctionKibanaContext; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionMovingAverage" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -420,11 +414,6 @@ export interface ExpressionFunctionDefinitions { var_set: ExpressionFunctionVarSet; } -// Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext>; - // Warning: (ae-missing-release-tag) "ExpressionFunctionParameter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -789,11 +778,6 @@ export { ExpressionValueRender } export { ExpressionValueRender as Render } -// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type ExpressionValueSearchContext = ExpressionValueBoxed<'kibana_context', ExecutionContextSearch>; - // Warning: (ae-missing-release-tag) "ExpressionValueUnboxed" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -908,7 +892,7 @@ export interface IExpressionLoaderParams { // (undocumented) onRenderError?: RenderErrorHandlerFnType; // (undocumented) - searchContext?: ExecutionContextSearch; + searchContext?: SerializableState_2; // (undocumented) searchSessionId?: string; // (undocumented) @@ -956,16 +940,6 @@ export interface IRegistry { // @public export function isExpressionAstBuilder(val: any): val is ExpressionAstExpressionBuilder; -// Warning: (ae-missing-release-tag) "KIBANA_CONTEXT_NAME" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type KIBANA_CONTEXT_NAME = 'kibana_context'; - -// Warning: (ae-missing-release-tag) "KibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type KibanaContext = ExpressionValueSearchContext; - // Warning: (ae-missing-release-tag) "KnownTypeToString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 1643b5734ef1a..4af36fea169a1 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -21,8 +21,8 @@ import { Adapters } from '../../../inspector/public'; import { IInterpreterRenderHandlers, ExpressionValue, - ExecutionContextSearch, ExpressionsService, + SerializableState, } from '../../common'; /** @@ -42,7 +42,7 @@ export interface ExpressionInterpreter { } export interface IExpressionLoaderParams { - searchContext?: ExecutionContextSearch; + searchContext?: SerializableState; context?: ExpressionValue; variables?: Record; // Enables debug tracking on each expression in the AST diff --git a/src/plugins/expressions/server/index.ts b/src/plugins/expressions/server/index.ts index cc22d4b500d97..287b91049b1ae 100644 --- a/src/plugins/expressions/server/index.ts +++ b/src/plugins/expressions/server/index.ts @@ -57,7 +57,6 @@ export { ExpressionFunction, ExpressionFunctionDefinition, ExpressionFunctionDefinitions, - ExpressionFunctionKibana, ExpressionFunctionParameter, ExpressionImage, ExpressionRenderDefinition, @@ -72,7 +71,6 @@ export { ExpressionValueError, ExpressionValueNum, ExpressionValueRender, - ExpressionValueSearchContext, ExpressionValueUnboxed, ExpressionValueFilter, Font, @@ -87,8 +85,6 @@ export { InterpreterErrorType, IRegistry, isExpressionAstBuilder, - KIBANA_CONTEXT_NAME, - KibanaContext, KnownTypeToString, Overflow, parse, diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 27a3193bf7894..e5b499206ebdd 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -128,15 +128,15 @@ export class Execution = StateContainer, ExecutionPureTransitions>; +// Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ExecutionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ExecutionContext { +export interface ExecutionContext { abortSignal: AbortSignal; // Warning: (ae-forgotten-export) The symbol "SavedObjectAttributes" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SavedObject" needs to be exported by the entry point index.d.ts getSavedObject?: (type: string, id: string) => Promise>; - // Warning: (ae-forgotten-export) The symbol "ExecutionContextSearch" needs to be exported by the entry point index.d.ts getSearchContext: () => ExecutionContextSearch; getSearchSessionId: () => string | undefined; inspectorAdapters: InspectorAdapters; @@ -368,12 +368,6 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) font: ExpressionFunctionFont; - // (undocumented) - kibana: ExpressionFunctionKibana; - // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionKibanaContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - kibana_context: ExpressionFunctionKibanaContext; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionMovingAverage" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -392,11 +386,6 @@ export interface ExpressionFunctionDefinitions { var_set: ExpressionFunctionVarSet; } -// Warning: (ae-missing-release-tag) "ExpressionFunctionKibana" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', ExpressionValueSearchContext | null, object, ExpressionValueSearchContext>; - // Warning: (ae-missing-release-tag) "ExpressionFunctionParameter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -640,11 +629,6 @@ export { ExpressionValueRender } export { ExpressionValueRender as Render } -// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type ExpressionValueSearchContext = ExpressionValueBoxed<'kibana_context', ExecutionContextSearch>; - // Warning: (ae-missing-release-tag) "ExpressionValueUnboxed" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -777,16 +761,6 @@ export interface IRegistry { // @public export function isExpressionAstBuilder(val: any): val is ExpressionAstExpressionBuilder; -// Warning: (ae-missing-release-tag) "KIBANA_CONTEXT_NAME" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type KIBANA_CONTEXT_NAME = 'kibana_context'; - -// Warning: (ae-missing-release-tag) "KibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type KibanaContext = ExpressionValueSearchContext; - // Warning: (ae-missing-release-tag) "KnownTypeToString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/src/plugins/data/common/utils/abort_utils.test.ts b/src/plugins/kibana_utils/common/abort_utils.test.ts similarity index 84% rename from src/plugins/data/common/utils/abort_utils.test.ts rename to src/plugins/kibana_utils/common/abort_utils.test.ts index 358f00e5e82bd..8ccbf752b4d1b 100644 --- a/src/plugins/data/common/utils/abort_utils.test.ts +++ b/src/plugins/kibana_utils/common/abort_utils.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AbortError, toPromise, getCombinedSignal } from './abort_utils'; +import { AbortError, abortSignalToPromise, getCombinedAbortSignal } from './abort_utils'; jest.useFakeTimers(); @@ -37,11 +37,11 @@ describe('AbortUtils', () => { }); }); - describe('toPromise', () => { + describe('abortSignalToPromise', () => { describe('rejects', () => { test('should not reject if the signal does not abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal).promise; + const promise = abortSignalToPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); await flushPromises(); @@ -50,7 +50,7 @@ describe('AbortUtils', () => { test('should reject if the signal does abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal).promise; + const promise = abortSignalToPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); controller.abort(); @@ -61,13 +61,13 @@ describe('AbortUtils', () => { test('should expose cleanup handler', () => { const controller = new AbortController(); - const promise = toPromise(controller.signal); + const promise = abortSignalToPromise(controller.signal); expect(promise.cleanup).toBeDefined(); }); test('calling clean up handler prevents rejects', async () => { const controller = new AbortController(); - const { promise, cleanup } = toPromise(controller.signal); + const { promise, cleanup } = abortSignalToPromise(controller.signal); const whenRejected = jest.fn(); promise.catch(whenRejected); cleanup(); @@ -78,9 +78,9 @@ describe('AbortUtils', () => { }); }); - describe('getCombinedSignal', () => { + describe('getCombinedAbortSignal', () => { test('should return an AbortSignal', () => { - const signal = getCombinedSignal([]).signal; + const signal = getCombinedAbortSignal([]).signal; expect(signal).toBeInstanceOf(AbortSignal); }); @@ -89,7 +89,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; + const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(500); await flushPromises(); @@ -101,7 +101,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; + const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(1000); await flushPromises(); @@ -112,7 +112,7 @@ describe('AbortUtils', () => { const controller1 = new AbortController(); const controller2 = new AbortController(); controller1.abort(); - const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; + const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(true); }); @@ -132,7 +132,7 @@ describe('AbortUtils', () => { const controller1 = createMockController(); const controller2 = createMockController(); - const { cleanup } = getCombinedSignal([ + const { cleanup } = getCombinedAbortSignal([ controller1.controller.signal, controller2.controller.signal, ]); @@ -150,7 +150,7 @@ describe('AbortUtils', () => { const controller1 = createMockController(); const controller2 = createMockController(); - getCombinedSignal([controller1.controller.signal, controller2.controller.signal]); + getCombinedAbortSignal([controller1.controller.signal, controller2.controller.signal]); expect(controller1.getTotalListeners()).toBe(1); expect(controller2.getTotalListeners()).toBe(1); diff --git a/src/plugins/data/common/utils/abort_utils.ts b/src/plugins/kibana_utils/common/abort_utils.ts similarity index 91% rename from src/plugins/data/common/utils/abort_utils.ts rename to src/plugins/kibana_utils/common/abort_utils.ts index 81f30b7454c7b..dd8db34931693 100644 --- a/src/plugins/data/common/utils/abort_utils.ts +++ b/src/plugins/kibana_utils/common/abort_utils.ts @@ -36,7 +36,9 @@ export class AbortError extends Error { * * @param signal The `AbortSignal` to generate the `Promise` from */ -export function toPromise(signal: AbortSignal): { promise: Promise; cleanup: () => void } { +export function abortSignalToPromise( + signal: AbortSignal +): { promise: Promise; cleanup: () => void } { let abortHandler: () => void; const cleanup = () => { if (abortHandler) { @@ -60,7 +62,7 @@ export function toPromise(signal: AbortSignal): { promise: Promise; clean * * @param signals */ -export function getCombinedSignal( +export function getCombinedAbortSignal( signals: AbortSignal[] ): { signal: AbortSignal; cleanup: () => void } { const controller = new AbortController(); @@ -69,7 +71,7 @@ export function getCombinedSignal( if (signals.some((signal) => signal.aborted)) { controller.abort(); } else { - const promises = signals.map((signal) => toPromise(signal)); + const promises = signals.map((signal) => abortSignalToPromise(signal)); cleanup = () => { promises.forEach((p) => p.cleanup()); controller.signal.removeEventListener('abort', cleanup); diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index e09290c811c7b..a49b7100594ba 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -24,6 +24,7 @@ export * from './ui'; export * from './state_containers'; export * from './typed_json'; export * from './errors'; +export { AbortError, abortSignalToPromise, getCombinedAbortSignal } from './abort_utils'; export { createGetterSetter, Get, Set } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; export { url } from './url'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 9ba42d39139da..46a0cc4a10f00 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -18,12 +18,15 @@ */ export { + AbortError, + abortSignalToPromise, calculateObjectHash, defer, Defer, fieldWildcardFilter, fieldWildcardMatcher, Get, + getCombinedAbortSignal, JsonArray, JsonObject, JsonValue, diff --git a/src/plugins/kibana_utils/server/index.ts b/src/plugins/kibana_utils/server/index.ts index bf3361d1e5369..d994a4940bdfd 100644 --- a/src/plugins/kibana_utils/server/index.ts +++ b/src/plugins/kibana_utils/server/index.ts @@ -18,10 +18,13 @@ */ export { + AbortError, + abortSignalToPromise, createGetterSetter, fieldWildcardFilter, fieldWildcardMatcher, Get, + getCombinedAbortSignal, Set, url, } from '../common'; diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md index 8393f7480d4e4..3e40c94e116fb 100644 --- a/src/plugins/ui_actions/public/public.api.md +++ b/src/plugins/ui_actions/public/public.api.md @@ -233,7 +233,7 @@ export class UiActionsService { // // (undocumented) protected readonly actions: ActionRegistry; - readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; + readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; // (undocumented) readonly attachAction: (triggerId: T, actionId: string) => void; readonly clear: () => void; @@ -247,21 +247,21 @@ export class UiActionsService { readonly executionService: UiActionsExecutionService; readonly fork: () => UiActionsService; // (undocumented) - readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; + readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">; // Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts // // (undocumented) readonly getTrigger: (triggerId: T) => TriggerContract; // (undocumented) - readonly getTriggerActions: (triggerId: T) => Action[]; + readonly getTriggerActions: (triggerId: T) => Action[]; // (undocumented) - readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; + readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; // (undocumented) readonly hasAction: (actionId: string) => boolean; // Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; + readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION">; // (undocumented) readonly registerTrigger: (trigger: Trigger) => void; // Warning: (ae-forgotten-export) The symbol "TriggerRegistry" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 1f0ac8b2b9392..7a630f36b51f4 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -18,8 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { TimeRange, Filter, esQuery, Query } from '../../../data/public'; +import { KibanaContext, TimeRange, Filter, esQuery, Query } from '../../../data/public'; import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; @@ -59,7 +58,7 @@ export interface TimelionSuccessResponse { sheet: Sheet[]; stats: Stats; visType: string; - type: KIBANA_CONTEXT_NAME; + type: KibanaContext['type']; } export function getTimelionRequestHandler({ diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts index a0cd410e197ff..2e8878b11e915 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts +++ b/src/plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -19,18 +19,14 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ExpressionFunctionDefinition, - KibanaContext, - Render, -} from 'src/plugins/expressions/public'; +import { ExpressionFunctionDefinition, Render } from 'src/plugins/expressions/public'; import { getTimelionRequestHandler, TimelionSuccessResponse, } from './helpers/timelion_request_handler'; import { TIMELION_VIS_NAME } from './timelion_vis_type'; import { TimelionVisDependencies } from './plugin'; -import { Filter, Query, TimeRange } from '../../data/common'; +import { KibanaContext, Filter, Query, TimeRange } from '../../data/public'; type Input = KibanaContext | null; type Output = Promise>; diff --git a/src/plugins/vis_type_timeseries/public/metrics_fn.ts b/src/plugins/vis_type_timeseries/public/metrics_fn.ts index b573225feaab1..8652d703f963e 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_fn.ts @@ -19,7 +19,8 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition, KibanaContext, Render } from '../../expressions/public'; +import { KibanaContext } from '../../data/public'; +import { ExpressionFunctionDefinition, Render } from '../../expressions/public'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; diff --git a/src/plugins/vis_type_vega/public/vega_fn.ts b/src/plugins/vis_type_vega/public/vega_fn.ts index 25d4e76c336b3..5a8113aeeea11 100644 --- a/src/plugins/vis_type_vega/public/vega_fn.ts +++ b/src/plugins/vis_type_vega/public/vega_fn.ts @@ -19,16 +19,12 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - ExecutionContext, - ExpressionFunctionDefinition, - KibanaContext, - Render, -} from '../../expressions/public'; +import { ExecutionContextSearch } from '../../data/public'; +import { ExecutionContext, ExpressionFunctionDefinition, Render } from '../../expressions/public'; import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; import { VegaInspectorAdapters } from './vega_inspector/index'; -import { TimeRange, Query } from '../../data/public'; +import { KibanaContext, TimeRange, Query } from '../../data/public'; import { VegaParser } from './data_model/vega_parser'; type Input = KibanaContext | null; @@ -51,7 +47,7 @@ export type VegaExpressionFunctionDefinition = ExpressionFunctionDefinition< Input, Arguments, Output, - ExecutionContext + ExecutionContext >; export const createVegaFn = ( diff --git a/x-pack/plugins/canvas/types/state.ts b/x-pack/plugins/canvas/types/state.ts index 60407b78ab5e3..03bb931dc9b26 100644 --- a/x-pack/plugins/canvas/types/state.ts +++ b/x-pack/plugins/canvas/types/state.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { KibanaContext } from 'src/plugins/data/common'; import { Datatable, ExpressionValueFilter, ExpressionImage, ExpressionFunction, - KibanaContext, PointSeries, Render, Style, diff --git a/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts b/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts index 2d5d09fd0205e..f38bec6d1f59f 100644 --- a/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts +++ b/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts @@ -8,11 +8,11 @@ import { of, merge, timer, throwError } from 'rxjs'; import { takeWhile, switchMap, expand, mergeMap, tap } from 'rxjs/operators'; import { - AbortError, doSearch, IKibanaSearchResponse, isErrorResponse, } from '../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../src/plugins/kibana_utils/common'; import type { IKibanaSearchRequest } from '../../../../../../src/plugins/data/common'; import type { IAsyncSearchOptions } from '../../../common/search/types'; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index f47d2b39a89a9..044489d58eb0e 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -7,7 +7,8 @@ import type { MockedKeys } from '@kbn/utility-types/jest'; import { coreMock } from '../../../../../src/core/public/mocks'; import { EnhancedSearchInterceptor } from './search_interceptor'; import { CoreSetup, CoreStart } from 'kibana/public'; -import { AbortError, UI_SETTINGS } from '../../../../../src/plugins/data/common'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/public'; import { SearchTimeoutError } from 'src/plugins/data/public'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; 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 4cafcdb29ae8d..d36741ae6576d 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -13,7 +13,7 @@ import { SearchInterceptorDeps, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; -import { AbortError, toPromise } from '../../../../../src/plugins/data/common'; +import { AbortError, abortSignalToPromise } from '../../../../../src/plugins/kibana_utils/public'; import { IAsyncSearchRequest, @@ -70,7 +70,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { abortSignal: options.abortSignal, timeout: this.searchTimeout, }); - const abortedPromise = toPromise(combinedSignal); + const abortedPromise = abortSignalToPromise(combinedSignal); const strategy = options?.strategy ?? ENHANCED_ES_SEARCH_STRATEGY; this.pendingCount$.next(this.pendingCount$.getValue() + 1); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 2e24b64ecca26..97165a8513078 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -23,7 +23,7 @@ import { IconType } from '@elastic/eui/src/components/icon/icon'; import { Ast, toExpression } from '@kbn/interpreter/common'; import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; -import { ExecutionContextSearch } from 'src/plugins/expressions'; +import { DataPublicPluginStart, ExecutionContextSearch } from 'src/plugins/data/public'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; @@ -33,7 +33,6 @@ import { } from '../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression } from './expression_helpers'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; -import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { validateDatasourceAndVisualization } from './state_helpers'; const MAX_SUGGESTIONS_DISPLAYED = 5; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index e0dd3b3fe01ae..00cb932a6d4e2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -20,7 +20,11 @@ import { EuiTitle, } from '@elastic/eui'; import { CoreStart, CoreSetup } from 'kibana/public'; -import { ExecutionContextSearch } from 'src/plugins/expressions'; +import { + DataPublicPluginStart, + ExecutionContextSearch, + TimefilterContract, +} from 'src/plugins/data/public'; import { ExpressionRendererEvent, ExpressionRenderError, @@ -44,10 +48,6 @@ import { VisualizeFieldContext, } from '../../../../../../../src/plugins/ui_actions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; -import { - DataPublicPluginStart, - TimefilterContract, -} from '../../../../../../../src/plugins/data/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; import { LensInspectorAdapters } from '../../types'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 33e5dee99081f..8139631daa971 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -8,6 +8,7 @@ import _ from 'lodash'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { + ExecutionContextSearch, Filter, IIndexPattern, Query, @@ -15,7 +16,6 @@ import { TimeRange, IndexPattern, } from 'src/plugins/data/public'; -import { ExecutionContextSearch } from 'src/plugins/expressions'; import { PaletteOutput } from 'src/plugins/charts/public'; import { Subscription } from 'rxjs'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index 7479577805bdd..4a3ba971381fb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -12,7 +12,7 @@ import { ExpressionRendererEvent, ReactExpressionRendererType, } from 'src/plugins/expressions/public'; -import { ExecutionContextSearch } from 'src/plugins/expressions'; +import { ExecutionContextSearch } from 'src/plugins/data/public'; import { getOriginalRequestErrorMessage } from '../error_helper'; export interface ExpressionWrapperProps { diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts index 07c16665d11b4..02c07d809e09f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts @@ -6,6 +6,7 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; +import { ExpressionValueSearchContext } from 'src/plugins/data/public'; import { Datatable, ExecutionContext } from 'src/plugins/expressions'; import { LensInspectorAdapters } from './types'; @@ -52,7 +53,7 @@ describe('lens_merge_tables', () => { const adapters: LensInspectorAdapters = { tables: {} }; mergeTables.fn(null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, { inspectorAdapters: adapters, - } as ExecutionContext); + } as ExecutionContext); expect(adapters.tables!.first).toBe(sampleTable1); expect(adapters.tables!.second).toBe(sampleTable2); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts index 03ef7cf9cc637..109b30144fc20 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts @@ -9,9 +9,8 @@ import { ExecutionContext, Datatable, ExpressionFunctionDefinition, - ExpressionValueSearchContext, } from 'src/plugins/expressions/public'; -import { search } from '../../../../../src/plugins/data/public'; +import { ExpressionValueSearchContext, search } from '../../../../../src/plugins/data/public'; const { toAbsoluteDates } = search.aggs; import { LensMultiTable } from '../types'; @@ -27,7 +26,7 @@ export const mergeTables: ExpressionFunctionDefinition< ExpressionValueSearchContext | null, MergeTables, LensMultiTable, - ExecutionContext + ExecutionContext > = { name: 'lens_merge_tables', type: 'lens_multitable', diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index d238e052a7c7f..730f8508e2889 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -29,7 +29,6 @@ import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionFunctionDefinition, ExpressionRenderDefinition, - ExpressionValueSearchContext, Datatable, DatatableRow, } from 'src/plugins/expressions/public'; @@ -45,7 +44,7 @@ import { import { XYArgs, SeriesType, visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart, getSeriesColor } from './state_helpers'; -import { parseInterval } from '../../../../../src/plugins/data/common'; +import { ExpressionValueSearchContext, search } from '../../../../../src/plugins/data/public'; import { ChartsPluginSetup, PaletteRegistry, @@ -384,7 +383,8 @@ export function XYChart({ // add minInterval only for single point in domain if (data.dateRange && isSingleTimestampInXDomain()) { const params = xAxisColumn?.meta?.sourceParams?.params as Record; - if (params?.interval !== 'auto') return parseInterval(params?.interval)?.asMilliseconds(); + if (params?.interval !== 'auto') + return search.aggs.parseInterval(params?.interval)?.asMilliseconds(); const { fromDate, toDate } = data.dateRange; const duration = moment(toDate).diff(moment(fromDate)); diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index 2d85c1d60a7b0..b6938bc18a88d 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -18,10 +18,10 @@ import { LastEventIndexKey, } from '../../../../../common/search_strategy/timeline'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { DocValueFields } from '../../../../../common/search_strategy'; diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 8db513da1f3a1..a2cf59314d149 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -18,11 +18,8 @@ import { MatrixHistogramStrategyResponse, MatrixHistogramData, } from '../../../../common/search_strategy/security_solution'; -import { - AbortError, - isErrorResponse, - isCompleteResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isErrorResponse, isCompleteResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 47e550b2ced0f..7e73a40f2f748 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -18,7 +18,7 @@ import { BrowserField, BrowserFields, } from '../../../../common/search_strategy/index_fields'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import * as i18n from './translations'; import { SourcererScopeName } from '../../store/sourcerer/model'; diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts index 93236381753bf..1f4424a4f28b8 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts @@ -11,11 +11,11 @@ import { takeUntil } from 'rxjs/operators'; import * as i18n from '../translations'; import { useKibana } from '../../../common/lib/kibana'; import { - AbortError, isCompleteResponse, isErrorResponse, isPartialResponse, } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { EqlSearchStrategyRequest, EqlSearchStrategyResponse, diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index 3cc87bdd275df..d964366dc5f3d 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -8,11 +8,8 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { HostsQueries } from '../../../../common/search_strategy/security_solution'; import { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx index 61c3f63bdb4aa..54381d1ffd836 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx @@ -21,10 +21,10 @@ import { import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index cc944a59571f1..1cb13da2048ad 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -17,10 +17,10 @@ import { import * as i18n from './translations'; import { DocValueFields } from '../../../../../common/search_strategy'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; const ID = 'firstLastSeenHostQuery'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 1228f94c7a39a..c1081d22e12a4 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -25,11 +25,8 @@ import { import { ESTermQuery } from '../../../../common/typed_json'; import * as i18n from './translations'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx index d433fef98bece..281f8489fce0f 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -19,7 +19,7 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx index 13d9bf368fc3d..ff4539fd379ed 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -19,7 +19,7 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx index fb1d0e05dd2a0..906a1d2716513 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -19,7 +19,7 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index ec792a6cad075..821b2895ac3f9 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -9,11 +9,8 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx index 2d5ed093ca1e4..8a80d073d4beb 100644 --- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx @@ -18,11 +18,8 @@ import { NetworkDetailsRequestOptions, NetworkDetailsStrategyResponse, } from '../../../../common/search_strategy'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx index 65f10a03cf13d..39868af2ae14d 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -20,10 +20,10 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx index a8f1a7abe2d44..3a4ce40fb8b80 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -20,10 +20,10 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx index 61861de9c0033..40cd04207b2d9 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -20,10 +20,10 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx index 594fc2a2adcfa..a2a64d0770558 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -20,10 +20,10 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx index 8484f1388caac..d183eb243e158 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -21,10 +21,10 @@ import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; import { - AbortError, isCompleteResponse, isErrorResponse, } from '../../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index 92a8f8c49dfc6..fc00f8866ed2e 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -22,11 +22,8 @@ import { NetworkDnsStrategyResponse, MatrixOverOrdinalHistogramData, } from '../../../../common/search_strategy/security_solution/network'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx index e4fa68b75999f..8edb760429a7c 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx @@ -23,11 +23,8 @@ import { NetworkHttpStrategyResponse, SortField, } from '../../../../common/search_strategy'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { InspectResponse } from '../../../types'; import { getInspectResponse } from '../../../helpers'; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx index 4b0ea2b82855b..0676d5976e211 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx @@ -23,11 +23,8 @@ import { NetworkTopCountriesStrategyResponse, PageInfoPaginated, } from '../../../../common/search_strategy'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx index 342fbbf67be4d..49ff6016900a5 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx @@ -23,11 +23,8 @@ import { NetworkTopNFlowStrategyResponse, PageInfoPaginated, } from '../../../../common/search_strategy'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 336aaec1d4bc9..8abd91186465a 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -21,11 +21,8 @@ import { NetworkTlsRequestOptions, NetworkTlsStrategyResponse, } from '../../../../common/search_strategy/security_solution/network'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; diff --git a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx index 8f21a23417aae..75f28773b89f6 100644 --- a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx @@ -23,11 +23,8 @@ import { NetworkUsersRequestOptions, NetworkUsersStrategyResponse, } from '../../../../common/search_strategy/security_solution/network'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx index 3c59ff7ba36c2..edf68750e2fdd 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx @@ -17,11 +17,8 @@ import { useKibana } from '../../../common/lib/kibana'; import { inputsModel } from '../../../common/store/inputs'; import { createFilter } from '../../../common/containers/helpers'; import { ESQuery } from '../../../../common/typed_json'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index 7f659db70277f..c414276c1a615 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -17,11 +17,8 @@ import { useKibana } from '../../../common/lib/kibana'; import { inputsModel } from '../../../common/store/inputs'; import { createFilter } from '../../../common/containers/helpers'; import { ESQuery } from '../../../../common/typed_json'; -import { - AbortError, - isCompleteResponse, - isErrorResponse, -} from '../../../../../../../src/plugins/data/common'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../../../src/plugins/kibana_utils/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d67cc06463942..e270996d62093 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1662,12 +1662,12 @@ "expressions.functions.font.invalidFontWeightErrorMessage": "無効なフォント太さ:'{weight}'", "expressions.functions.font.invalidTextAlignmentErrorMessage": "無効なテキストアラインメント:'{align}'", "expressions.functions.fontHelpText": "フォントスタイルを作成します。", - "expressions.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", - "expressions.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", - "expressions.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", - "expressions.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", - "expressions.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", - "expressions.functions.kibana.help": "Kibana グローバルコンテキストを取得します", + "data.search.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", + "data.search.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", + "data.search.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", + "data.search.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", + "data.search.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", + "data.search.functions.kibana.help": "Kibana グローバルコンテキストを取得します", "expressions.functions.theme.args.defaultHelpText": "テーマ情報がない場合のデフォルト値。", "expressions.functions.theme.args.variableHelpText": "読み取るテーマ変数名。", "expressions.functions.themeHelpText": "テーマ設定を読み取ります。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 103aef656c4e1..7bf0a22a4dce7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1663,12 +1663,12 @@ "expressions.functions.font.invalidFontWeightErrorMessage": "无效的字体粗细:“{weight}”", "expressions.functions.font.invalidTextAlignmentErrorMessage": "无效的文本对齐方式:“{align}”", "expressions.functions.fontHelpText": "创建字体样式。", - "expressions.functions.kibana_context.filters.help": "指定 Kibana 常规筛选", - "expressions.functions.kibana_context.help": "更新 kibana 全局上下文", - "expressions.functions.kibana_context.q.help": "指定 Kibana 自由格式文本查询", - "expressions.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", - "expressions.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", - "expressions.functions.kibana.help": "获取 kibana 全局上下文", + "data.search.functions.kibana_context.filters.help": "指定 Kibana 常规筛选", + "data.search.functions.kibana_context.help": "更新 kibana 全局上下文", + "data.search.functions.kibana_context.q.help": "指定 Kibana 自由格式文本查询", + "data.search.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", + "data.search.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", + "data.search.functions.kibana.help": "获取 kibana 全局上下文", "expressions.functions.theme.args.defaultHelpText": "主题信息不可用时的默认值。", "expressions.functions.theme.args.variableHelpText": "要读取的主题变量的名称。", "expressions.functions.themeHelpText": "读取主题设置。", From ae6f5afe8d4e1d8f8ee5af7712295d78ec4850be Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Mon, 9 Nov 2020 15:58:16 -0600 Subject: [PATCH 33/87] load empty_kibana in test to have clean starting point (#82772) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/apps/management/_mgmt_import_saved_objects.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.js index 3a9f8665fd33b..d479a7006d0f8 100644 --- a/test/functional/apps/management/_mgmt_import_saved_objects.js +++ b/test/functional/apps/management/_mgmt_import_saved_objects.js @@ -29,6 +29,7 @@ export default function ({ getService, getPageObjects }) { describe('mgmt saved objects', function describeIndexTests() { beforeEach(async function () { + await esArchiver.load('empty_kibana'); await esArchiver.load('discover'); await PageObjects.settings.navigateTo(); }); From 04c583e2b0ccef54bd25803b021b8e580fee6c90 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 9 Nov 2020 14:10:22 -0800 Subject: [PATCH 34/87] [App Search] Misc naming tech debt (#82770) * Rename `engine_overview` folder to `engines` - To better match ent-search folder structure - Also rename/clarify pluralization of "Engines" where possible, to distinguish between individual Engine Overview pages * DRY out Engines and Meta Engines titles * DRY out Credentials title * DRY out Settings title * DRY out Role Mappings title * Update localization keys to match changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/credentials/constants.ts | 6 ++++ .../components/credentials/credentials.tsx | 15 ++------- .../components/credentials/index.ts | 1 + .../app_search/components/engine/constants.ts | 4 --- .../components/engine/engine_nav.tsx | 2 +- .../assets/engine_icon.tsx | 0 .../assets/meta_engine_icon.tsx | 0 .../components/empty_state.scss | 0 .../components/empty_state.test.tsx | 0 .../components/empty_state.tsx | 4 +-- .../components/header.test.tsx | 8 ++--- .../components/header.tsx | 4 +-- .../components/index.ts | 2 +- .../components/loading_state.test.tsx | 0 .../components/loading_state.tsx | 4 +-- .../components/engines/constants.ts | 16 +++++++++ .../engines_overview.scss} | 2 +- .../engines_overview.test.tsx} | 24 +++++++------- .../engines_overview.tsx} | 29 +++++++--------- .../engines_table.test.tsx} | 11 ++++--- .../engines_table.tsx} | 16 ++++----- .../app_search/components/engines/index.ts | 8 +++++ .../components/role_mappings/constants.ts | 12 +++++++ .../index.ts | 2 +- .../components/settings/constants.ts | 11 +++++++ .../app_search/components/settings/index.ts | 1 + .../components/settings/settings.tsx | 17 +++------- .../applications/app_search/index.test.tsx | 4 +-- .../public/applications/app_search/index.tsx | 33 +++++-------------- .../translations/translations/ja-JP.json | 11 +++---- .../translations/translations/zh-CN.json | 11 +++---- 31 files changed, 135 insertions(+), 123 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/assets/engine_icon.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/assets/meta_engine_icon.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/empty_state.scss (100%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/empty_state.test.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/empty_state.tsx (96%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/header.test.tsx (82%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/header.tsx (93%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/index.ts (87%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/loading_state.test.tsx (100%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => engines}/components/loading_state.tsx (89%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview/engine_overview.scss => engines/engines_overview.scss} (96%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview/engine_overview.test.tsx => engines/engines_overview.test.tsx} (80%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview/engine_overview.tsx => engines/engines_overview.tsx} (83%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview/engine_table.test.tsx => engines/engines_table.test.tsx} (91%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview/engine_table.tsx => engines/engines_table.tsx} (92%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts rename x-pack/plugins/enterprise_search/public/applications/app_search/components/{engine_overview => role_mappings}/index.ts (82%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts index decf1e2158744..ea4906ec08946 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts @@ -3,8 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { i18n } from '@kbn/i18n'; +export const CREDENTIALS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.credentials.title', + { defaultMessage: 'Credentials' } +); + export enum ApiTokenTypes { Admin = 'admin', Private = 'private', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx index c8eae8cc13f5f..72b02dfdc1f61 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx @@ -28,6 +28,7 @@ import { FlashMessages } from '../../../shared/flash_messages'; import { CredentialsLogic } from './credentials_logic'; import { externalUrl } from '../../../shared/enterprise_search_url/external_url'; +import { CREDENTIALS_TITLE } from './constants'; import { CredentialsList } from './credentials_list'; import { CredentialsFlyout } from './credentials_flyout'; @@ -47,21 +48,11 @@ export const Credentials: React.FC = () => { return ( <> - + -

- {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.title', { - defaultMessage: 'Credentials', - })} -

+

{CREDENTIALS_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts index bceda234175a7..0fc64a43ffa85 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts @@ -5,3 +5,4 @@ */ export { Credentials } from './credentials'; +export { CREDENTIALS_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts index ee5b47eda490e..3c963e415f33b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts @@ -9,10 +9,6 @@ import { i18n } from '@kbn/i18n'; // TODO: It's very likely that we'll move these i18n constants to their respective component // folders once those are migrated over. This is a temporary way of DRYing them out for now. -export const ENGINES_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engines.title', { - defaultMessage: 'Engines', -}); - export const OVERVIEW_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.overview.title', { defaultMessage: 'Overview' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index e5ee392b34c01..f92fefc7124b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -30,8 +30,8 @@ import { ENGINE_API_LOGS_PATH, } from '../../routes'; import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; +import { ENGINES_TITLE } from '../engines'; import { - ENGINES_TITLE, OVERVIEW_TITLE, ANALYTICS_TITLE, DOCUMENTS_TITLE, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/assets/engine_icon.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/engine_icon.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/assets/engine_icon.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/meta_engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/assets/meta_engine_icon.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/assets/meta_engine_icon.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/assets/meta_engine_icon.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.scss similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx similarity index 96% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx index b7ed1cc895097..95142782d2272 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx @@ -14,7 +14,7 @@ import { getAppSearchUrl } from '../../../../shared/enterprise_search_url'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { CREATE_ENGINES_PATH } from '../../../routes'; -import { EngineOverviewHeader } from './header'; +import { EnginesOverviewHeader } from './header'; import './empty_state.scss'; @@ -34,7 +34,7 @@ export const EmptyState: React.FC = () => { return ( <> - + { +describe('EnginesOverviewHeader', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('h1')).toHaveLength(1); }); it('renders a launch app search button that sends telemetry on click', () => { - const wrapper = shallow(); + const wrapper = shallow(); const button = wrapper.find('[data-test-subj="launchButton"]'); expect(button.prop('href')).toBe('http://localhost:3002/as'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx similarity index 93% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx index 4bb69bafa0996..3faa74be11e98 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx @@ -19,7 +19,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TelemetryLogic } from '../../../../shared/telemetry'; import { getAppSearchUrl } from '../../../../shared/enterprise_search_url'; -export const EngineOverviewHeader: React.FC = () => { +export const EnginesOverviewHeader: React.FC = () => { const { sendAppSearchTelemetry } = useActions(TelemetryLogic); const buttonProps = { @@ -42,7 +42,7 @@ export const EngineOverviewHeader: React.FC = () => {

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts similarity index 87% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts index 794053f184f8c..cb2e0f825d455 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EngineOverviewHeader } from './header'; +export { EnginesOverviewHeader } from './header'; export { LoadingState } from './loading_state'; export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/loading_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.test.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/loading_state.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/loading_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx similarity index 89% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/loading_state.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx index 832a133ed43c7..8e2166d2ef105 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/loading_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; -import { EngineOverviewHeader } from './header'; +import { EnginesOverviewHeader } from './header'; export const LoadingState: React.FC = () => { return ( <> - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts new file mode 100644 index 0000000000000..12545c59b40a0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts @@ -0,0 +1,16 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ENGINES_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engines.title', { + defaultMessage: 'Engines', +}); + +export const META_ENGINES_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.metaEngines.title', + { defaultMessage: 'Meta Engines' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.scss similarity index 96% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.scss index 5e8a20ba425ad..c956f3da562f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.scss @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -.engineOverview { +.enginesOverview { padding: $euiSize; @include euiBreakpoint('m', 'l', 'xl') { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx similarity index 80% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx index f87ea2d422780..61f783a8b6c2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx @@ -14,14 +14,14 @@ import { shallow, ReactWrapper } from 'enzyme'; import { mountAsync, mockHttpValues, setMockValues } from '../../../__mocks__'; import { LoadingState, EmptyState } from './components'; -import { EngineTable } from './engine_table'; +import { EnginesTable } from './engines_table'; -import { EngineOverview } from './'; +import { EnginesOverview } from './'; -describe('EngineOverview', () => { +describe('EnginesOverview', () => { describe('non-happy-path states', () => { it('isLoading', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(LoadingState)).toHaveLength(1); }); @@ -36,7 +36,7 @@ describe('EngineOverview', () => { }), }, }); - const wrapper = await mountAsync(, { i18n: true }); + const wrapper = await mountAsync(, { i18n: true }); expect(wrapper.find(EmptyState)).toHaveLength(1); }); @@ -69,9 +69,9 @@ describe('EngineOverview', () => { }); it('renders and calls the engines API', async () => { - const wrapper = await mountAsync(, { i18n: true }); + const wrapper = await mountAsync(, { i18n: true }); - expect(wrapper.find(EngineTable)).toHaveLength(1); + expect(wrapper.find(EnginesTable)).toHaveLength(1); expect(mockApi).toHaveBeenNthCalledWith(1, '/api/app_search/engines', { query: { type: 'indexed', @@ -86,9 +86,9 @@ describe('EngineOverview', () => { hasPlatinumLicense: true, http: { ...mockHttpValues.http, get: mockApi }, }); - const wrapper = await mountAsync(, { i18n: true }); + const wrapper = await mountAsync(, { i18n: true }); - expect(wrapper.find(EngineTable)).toHaveLength(2); + expect(wrapper.find(EnginesTable)).toHaveLength(2); expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', { query: { type: 'meta', @@ -100,10 +100,10 @@ describe('EngineOverview', () => { describe('pagination', () => { const getTablePagination = (wrapper: ReactWrapper) => - wrapper.find(EngineTable).prop('pagination'); + wrapper.find(EnginesTable).prop('pagination'); it('passes down page data from the API', async () => { - const wrapper = await mountAsync(, { i18n: true }); + const wrapper = await mountAsync(, { i18n: true }); const pagination = getTablePagination(wrapper); expect(pagination.totalEngines).toEqual(100); @@ -111,7 +111,7 @@ describe('EngineOverview', () => { }); it('re-polls the API on page change', async () => { - const wrapper = await mountAsync(, { i18n: true }); + const wrapper = await mountAsync(, { i18n: true }); await act(async () => getTablePagination(wrapper).onPaginate(5)); wrapper.update(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx similarity index 83% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index c0fd254567910..559fef695d63b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -13,7 +13,6 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; @@ -23,11 +22,11 @@ import { LicensingLogic } from '../../../shared/licensing'; import { EngineIcon } from './assets/engine_icon'; import { MetaEngineIcon } from './assets/meta_engine_icon'; +import { ENGINES_TITLE, META_ENGINES_TITLE } from './constants'; +import { EnginesOverviewHeader, LoadingState, EmptyState } from './components'; +import { EnginesTable } from './engines_table'; -import { EngineOverviewHeader, LoadingState, EmptyState } from './components'; -import { EngineTable } from './engine_table'; - -import './engine_overview.scss'; +import './engines_overview.scss'; interface IGetEnginesParams { type: string; @@ -38,7 +37,7 @@ interface ISetEnginesCallbacks { setResultsTotal: React.Dispatch>; } -export const EngineOverview: React.FC = () => { +export const EnginesOverview: React.FC = () => { const { http } = useValues(HttpLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); @@ -88,21 +87,18 @@ export const EngineOverview: React.FC = () => { - - + +

- - {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.engines', { - defaultMessage: 'Engines', - })} + {ENGINES_TITLE}

- {

- - {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines', { - defaultMessage: 'Meta Engines', - })} + {META_ENGINES_TITLE}

- { +describe('EnginesTable', () => { const onPaginate = jest.fn(); // onPaginate updates the engines API call upstream const wrapper = mountWithIntl( - { it('handles empty data', () => { const emptyWrapper = mountWithIntl( - {} }} /> + {} }} + /> ); const emptyTable = emptyWrapper.find(EuiBasicTable); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx similarity index 92% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index abeaf45e6aee8..a9cf64b3dffda 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -16,28 +16,28 @@ import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; -export interface IEngineTableData { +interface IEnginesTableData { name: string; created_at: string; document_count: number; field_count: number; } -export interface IEngineTablePagination { +interface IEnginesTablePagination { totalEngines: number; pageIndex: number; onPaginate(pageIndex: number): void; } -export interface IEngineTableProps { - data: IEngineTableData[]; - pagination: IEngineTablePagination; +interface IEnginesTableProps { + data: IEnginesTableData[]; + pagination: IEnginesTablePagination; } -export interface IOnChange { +interface IOnChange { page: { index: number; }; } -export const EngineTable: React.FC = ({ +export const EnginesTable: React.FC = ({ data, pagination: { totalEngines, pageIndex, onPaginate }, }) => { @@ -52,7 +52,7 @@ export const EngineTable: React.FC = ({ }), }); - const columns: Array> = [ + const columns: Array> = [ { field: 'name', name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts new file mode 100644 index 0000000000000..76ca9239a3c7e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { EnginesOverview } from './engines_overview'; +export { ENGINES_TITLE, META_ENGINES_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts new file mode 100644 index 0000000000000..133d8b604381b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ROLE_MAPPINGS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.roleMappings.title', + { defaultMessage: 'Role Mappings' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts similarity index 82% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts index 48b7645dc39e8..662b91bb3e925 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EngineOverview } from './engine_overview'; +export { ROLE_MAPPINGS_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts new file mode 100644 index 0000000000000..c7b22d740341c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SETTINGS_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.settings.title', { + defaultMessage: 'Settings', +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts index db74dcb1a1846..3e162a4a2a1bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts @@ -5,3 +5,4 @@ */ export { Settings } from './settings'; +export { SETTINGS_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx index e5e86f3e39734..13079bb380f13 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx @@ -6,30 +6,21 @@ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiPageHeader, EuiPageHeaderSection, EuiPageContentBody, EuiTitle } from '@elastic/eui'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { FlashMessages } from '../../../shared/flash_messages'; +import { SETTINGS_TITLE } from './'; + export const Settings: React.FC = () => { return ( <> - + -

- {i18n.translate('xpack.enterpriseSearch.appSearch.settings.title', { - defaultMessage: 'Settings', - })} -

+

{SETTINGS_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 700b903efe59b..11387734e9f9e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -16,7 +16,7 @@ import { shallow } from 'enzyme'; import { Layout, SideNav, SideNavLink } from '../shared/layout'; import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; -import { EngineOverview } from './components/engine_overview'; +import { EnginesOverview } from './components/engines'; import { EngineRouter } from './components/engine'; import { AppSearch, AppSearchUnconfigured, AppSearchConfigured, AppSearchNav } from './'; @@ -57,7 +57,7 @@ describe('AppSearchConfigured', () => { expect(wrapper.find(Layout)).toHaveLength(2); expect(wrapper.find(Layout).last().prop('readOnlyMode')).toBeFalsy(); - expect(wrapper.find(EngineOverview)).toHaveLength(1); + expect(wrapper.find(EnginesOverview)).toHaveLength(1); expect(wrapper.find(EngineRouter)).toHaveLength(1); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index f32b0b256b898..4571ef10286e4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -8,8 +8,6 @@ import React, { useEffect } from 'react'; import { Route, Redirect, Switch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { i18n } from '@kbn/i18n'; - import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { KibanaLogic } from '../shared/kibana'; import { HttpLogic } from '../shared/http'; @@ -33,9 +31,10 @@ import { import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; import { NotFound } from '../shared/not_found'; -import { EngineOverview } from './components/engine_overview'; -import { Settings } from './components/settings'; -import { Credentials } from './components/credentials'; +import { EnginesOverview, ENGINES_TITLE } from './components/engines'; +import { Settings, SETTINGS_TITLE } from './components/settings'; +import { Credentials, CREDENTIALS_TITLE } from './components/credentials'; +import { ROLE_MAPPINGS_TITLE } from './components/role_mappings'; export const AppSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); @@ -82,7 +81,7 @@ export const AppSearchConfigured: React.FC = (props) => { - + @@ -113,29 +112,15 @@ export const AppSearchNav: React.FC = ({ subNav }) => { return ( - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.engines', { - defaultMessage: 'Engines', - })} + {ENGINES_TITLE} - {canViewSettings && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.settings', { - defaultMessage: 'Settings', - })} - - )} + {canViewSettings && {SETTINGS_TITLE}} {canViewAccountCredentials && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.credentials', { - defaultMessage: 'Credentials', - })} - + {CREDENTIALS_TITLE} )} {canViewRoleMappings && ( - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.roleMappings', { - defaultMessage: 'Role Mappings', - })} + {ROLE_MAPPINGS_TITLE} )} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e270996d62093..d8e136567564e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6930,8 +6930,8 @@ "xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta": "エンジンを作成", "xpack.enterpriseSearch.appSearch.emptyState.description1": "App Searchエンジンは、検索エクスペリエンスのために、ドキュメントを格納します。", "xpack.enterpriseSearch.appSearch.emptyState.title": "初めてのエンジンの作成", - "xpack.enterpriseSearch.appSearch.enginesOverview.engines": "エンジン", - "xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines": "メタエンジン", + "xpack.enterpriseSearch.appSearch.engines.title": "エンジン", + "xpack.enterpriseSearch.appSearch.metaEngines.title": "メタエンジン", "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage": "管理", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions": "アクション", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt": "作成日時", @@ -6939,10 +6939,9 @@ "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount": "フィールドカウント", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name": "名前", "xpack.enterpriseSearch.appSearch.enginesOverview.title": "エンジン概要", - "xpack.enterpriseSearch.appSearch.nav.credentials": "資格情報", - "xpack.enterpriseSearch.appSearch.nav.engines": "エンジン", - "xpack.enterpriseSearch.appSearch.nav.roleMappings": "ロールマッピング", - "xpack.enterpriseSearch.appSearch.nav.settings": "アカウント設定", + "xpack.enterpriseSearch.appSearch.credentials.title": "資格情報", + "xpack.enterpriseSearch.appSearch.roleMappings.title": "ロールマッピング", + "xpack.enterpriseSearch.appSearch.settings.title": "アカウント設定", "xpack.enterpriseSearch.appSearch.productCardDescription": "Elastic App Searchには、強力な検索を設計し、WebサイトやWeb/モバイルアプリケーションにデプロイするための使いやすいツールがあります。", "xpack.enterpriseSearch.appSearch.productCta": "App Searchの起動", "xpack.enterpriseSearch.appSearch.productDescription": "ダッシュボード、分析、APIを活用し、高度なアプリケーション検索をシンプルにします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7bf0a22a4dce7..84cc3510ce487 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6936,8 +6936,8 @@ "xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta": "创建引擎", "xpack.enterpriseSearch.appSearch.emptyState.description1": "App Search 引擎存储文档以提升您的搜索体验。", "xpack.enterpriseSearch.appSearch.emptyState.title": "创建您的首个引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.engines": "引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines": "元引擎", + "xpack.enterpriseSearch.appSearch.engines.title": "引擎", + "xpack.enterpriseSearch.appSearch.metaEngines.title": "元引擎", "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage": "管理", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions": "操作", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt": "创建于", @@ -6945,10 +6945,9 @@ "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount": "字段计数", "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name": "名称", "xpack.enterpriseSearch.appSearch.enginesOverview.title": "引擎概览", - "xpack.enterpriseSearch.appSearch.nav.credentials": "凭据", - "xpack.enterpriseSearch.appSearch.nav.engines": "引擎", - "xpack.enterpriseSearch.appSearch.nav.roleMappings": "角色映射", - "xpack.enterpriseSearch.appSearch.nav.settings": "帐户设置", + "xpack.enterpriseSearch.appSearch.credentials.title": "凭据", + "xpack.enterpriseSearch.appSearch.roleMappings.title": "角色映射", + "xpack.enterpriseSearch.appSearch.settings.title": "帐户设置", "xpack.enterpriseSearch.appSearch.productCardDescription": "Elastic App Search 提供用户友好的工具,用于设计强大的搜索功能,并将其部署到您的网站或 Web/移动应用程序。", "xpack.enterpriseSearch.appSearch.productCta": "启动 App Search", "xpack.enterpriseSearch.appSearch.productDescription": "利用仪表板、分析和 API 执行高级应用程序搜索简单易行。", From bf758312cdba1986a4cd99cc6c011185f66ec15e Mon Sep 17 00:00:00 2001 From: IgorG <56408662+IgorGuz2000@users.noreply.github.com> Date: Mon, 9 Nov 2020 14:35:16 -0800 Subject: [PATCH 35/87] New events resolver (#82170) * Added Test for event.library * renamed data directry and gzip data file * rename expectedData file * Changes per Charlie request * Changes for the enable_APM-ci branch * Update resolver.ts * Added comment per Charlie request * Update resolver.ts * Added Alert Test for Resolver and fix for the APM enabled Run fail * Added Alert Test for Resolver and fix for the APM enabled Run fail * removed commented out code * Fixing CI fail * Fixing CI fail * Removed Alert Resolver test * aAdding Alert test back * Adding Alert test back * Adding Alert test back * Adding info log for debuging * Adding info log for debuging * Adding info log for debuging * Adding info log for debuging * Adding info log for debuging * Adding info log for debuging * adding one more verification for Data * stripedd Data file Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../resolver_tree/alert_events/data.json.gz | Bin 0 -> 108120 bytes .../apps/endpoint/resolver.ts | 23 ++++++++++++++++++ .../test/security_solution_endpoint/config.ts | 1 - .../page_objects/hosts_page.ts | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/es_archives/endpoint/resolver_tree/alert_events/data.json.gz diff --git a/x-pack/test/functional/es_archives/endpoint/resolver_tree/alert_events/data.json.gz b/x-pack/test/functional/es_archives/endpoint/resolver_tree/alert_events/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..997a7a5322a11545a2fb1cf33f051d964e35391c GIT binary patch literal 108120 zcmagFbyQnH`!yQe9g4dQ3fz~@%yjMNTpI= z4V=+f@btD+dS1uX{50|aMB1Ah=+o8JA=Km^UPGOpS+@$b2HqWyMVd|6FWb6{Ek}kj z>wl?eo>)toF|-qT%D9!?G1@SAF}&X;g?OL;Iko}SQQ z#vDBt8yt-Lwd|rN`^FpG7K*c#8Ao&=V61A+IBP}mXPo860k1}2ZNU;BV~Bu^wOl|p ze_NGFLWWS0Q<3+3aTs0Krgkv@5pZywygu+^1&@qkH1gAC=p5?U+lhUh!}EjbO&4e! za6P`0F{(fqvqn-y7W=`}R!|M&h?7BPJwyIx_Nd9eg=^$s&?)Q~dsqa+yIPh`jMm)*l(!r%aJRz;h0zHgR%SAx_=8ig?Z(N*zIlam=@nS?rOg}TgfWj-v;Z;rAH9uJYG z^ToAgen;Txd-LnSH1Ub@DfjdQ(&h#Axvi>(d3 zCB>&CqtfN3!M5qWYIa`7Zc5f-1IArQL|OFmdJSvzauP{XDua_ov?H|+YH12?1(_VD z3yyPckQ`|=_1MPx1U-jpXT!`GqAZ!S;<5_H`&khF$R^KsR+W$s{{1bN>!-mBJmv)M zu8qy6lA5z2@$#wK-@A)_1j?x@N7v!>_l)%~N6kJUkwRDw(z8&GZ%@mn!7Zzsag_#& z6AzSNjx{-XyW!1!=k2xcBHcAiirU|6*#Zuh$Q_mqw`07w7D~E?W>kb4n!rqV3p`pz zP4z8Jqg?S?Eo?O57koXZ7SX8>YO%nr^S8pXIi+145>|CN_Pamh45z8E71w=Z^|WgB zMSqW2GM;JYV;)>+-3(l9&z-e-2qXp(Djz{&3??g(#n76h8yDK3!FDIMV93+mnz4=J zy@T)a0W_lHg!;j>pw0d?qO82JN$6-+W28mxClI=L2VR+&9v!fm_?^ov1at_;@qV}w zVt&wCRf4=-Ze0ER00f#m>@K=fU#HfOv^2C z`nD^()Fe74^3o{=MTgy2AdJYO{6ce`gm7#`|HBmYT+Cq{tFdD*#8@4l7OjSS$(K=q zfN21ksKTT!Jq&9W6eim?-y%zvZorgl@pQ|0=CN^;GYmYpcrux41W#}Bu2MjN^NpLW zs~3?+86+kZ;$ZIPKFh$NkS(|U%cF+7;~$^s%bi+(%(|u4UIrSy6);4Q#iT~9)U#+@ zx-6NrqpV$M3FGx>mMVvShN`(}t$aA-2q&*?In1jgH)yo3==BJ!L9VYYYlo^~G=ql) zz-5_Nr8+D;qmfjU30aZQ|2s`KV|TVW9bt8-)?JurD%dANtK7mSdWCzg*ZsQ zas+P3+2wIn@ena^ZB%L9A$Tq|4pSy{?DK9T`b;Gi9E?7T)x%;XL%B#gRsdG8L0l*aks-1nnFWn_eIbkt8GmcdlT=7P^YQKG z{7h|1G|gAn_k;bs`c|J47{VItKN8?;fx8==Q9kw7=eQb{&Cgf*;gLR6!_@yFZed2LY!+PfYd1nvDm_I0zF zS~nXoa&xAHuT+Y&7ZNnRnGa3pS2+nlfM`QEY zI_SfaZvXUtuiajtrHB0M^bGC7L`r{Wev)Jo!6E$t7n`cb=Y_ZOR9SCl&ao85Y)=+4 z#=82b$q$oRC$^9s9TiRrsc8OApb$Lf`a zH_nZpIF!f51!V~2tkzoVKx9MFVVv@2`z2FTN&^JD75Tzg6<`ccy2ci{g}_P{sQ8p( z0K|mSAr!GvuOK?%eDU;-_^#4+JcQ!2(b;+#{3VY&-Y;+BcQc8#qe^;j$`^o7iNBPW zi2hXA+56r$WVK<})DLH9fw#(c$(jrcqAGJsA02#rc$Bzu?GHD8&{B*3*x{5%2*0+&VBwd^UImBj zd8j!Ys!|hAfe~2^*VlohKEx;AHf((%;5MS+?eNvsnSUSxJG|Tm1J8U)N^g4Kclkus z{;;|DzW4%J(@)mUg&vMyHtE17t}pZr>;7o+^2}CsHopZkCG}-w>c|)jq|=2O$W7iO zxR6D?ZON58=+!V8?(y8&>r((9JMSj0X|R>2AC}C^4#d z6H~tMUN!jyK2p!%Cfdzw1x$I+qO^N-7KE8oMsL1=bSMhgXN*}wN_&{`a&U9xsSatd zUA73GS`r}IEp=d?mhT%gOx^?9RLnd1I9RM|AG`50@O-qU&_@d8VrS^MGYb|Y^x54z zGyBB6wfzD2s*mP=6D=mRy=BDQIRW~GI)o~Mb37&>e3><#;wombEz)9s(`t8Rz<^z+ zA}me;d;Z3-$Vn>9>7XRSFzX{&?YeT_upB?VHb-;!{-+kRFqN!U701@)*B#-jqlOy; zH&@?n>tX%iwTkT(Eiu(jd3Rc-;}s|M2r_=*ef@0@enGThf!~3Ju6jv@p@BBLXYUaw zvfmy1rY}vnNMExwra!`I1b%-@EUC-c;QJVc!`#~zx~jM^Fda$XMk6G@FyF6yws~`Q zzWxex1!7*mvSc?j%iJO64Q+0jKhENU?maza0Uvj%p|@@3(yd;*3+7K{+p5Yp_8oXU zQo0s*cvY3NVqoSNkcbPnZMEAF%9F%)g9r8Ci^b(f-o5uGBQ44|&1r?RLd;qAY0MAq zLf$dHEb=W+6b8IFirUp)nWfOnS8u;0_3oQjv)8!~XRL=T>nIK08JhG$s+C3o^ephNTiCKicI}xCom3=d0Tz+l7=}po8|=r&NTF zmP+=wUk~T|G9woBd252EAVUcU^<`NFN<=@p{bzB{R|zUUU3x3{@!yM#pR6mkNt^II z?mlgHy%#*v$z1YR4a?lLo6C!^@N$>#*H1HHj0@;_yY`m6i>??=yYmx?CGjqW8v{<* zD_L7x+>pUkL+TJY4n{#;oF<{NpVb<|G9Gye7$FQFzkjPX1s`)p=4eZ6I^svWxifs1 z=XxodAEAR=#GsfHl)}KsrQ~AQ#kx!xJSN0gkID36nqSPPD8nrP#$~WeV>my&;UY0Q zuOu5|xQpT8Gg43yfBDAICKN#%V<+VM*c&RspSe$y-`3<<4RA{^+%wn)kTS^d?>B8+ zEI?@=y<*{RU%Udm64xMY$CVEfNA_63#FbiZjx22?0H~3n0sdl57R~tgLJAlt?@D7z zhC!FIE-@@gg3Md%uPGcGp9*p*{=md4jOja#Q{&?!<4@+7HF$v)jdhN#@R{Z$74!Cu+ITd^CfAJDr6 zXbvQJvy}TjDaBgH^ZrfdUfaCy>Ll>gpJM-_?N23xKrkf9`(f~MtlRO`uG-Z}#FLdi z)VuP;9b6AiV>Ul9nl65zO(MJp8a|mJ0w9%Gt!%f{kUms^6rf}fV1`aI0*LbZ4wuKX zqmM$CrFMFGn6p*`0?1?W%( zIg0`G9f4gTH@>zuR4a22NPKUE^Hd*KDtoHciMhHHerh3_@XPb3TeFK8 z93=1+=VyrRd;9^0VVsMuM;@`i19$%;{V}xYj(Yc%maX3`K1r#k)lN(h^A0pjN9@qn zuR=j*$Dt|kb;sjPobv&+r)OuB3A^^-k!WiQnJB=-Zw20`%p@L-nZE=QMvg zB}LDj`pjhL1xhlIb2w`xPtYzSLlM-5TUMqcn)Q<|rUPi<5(!>;2%`MCWb?$z^PF9A`6%@zzRdd7U z`C644pM=nQ7mM(23$O($aR$*v=yZd2W8hSFe@Bym{^B(1rSY$Y6e_@{&fNCzhCRh= zqxWbpt#HExl)@6xOnT*h`~kf*wUV^z4|A+j7F7EYrpmYO7yin7i1$S-8!7qzhB%!i zMZSOd2E&AZ)qJX{s9#$*O}0wLc2L^Y|MT;DTp740YBf z&^QK`rf@j40ts(7fWuGz&~<3i7El2`_1qSgd7;pkXhl#t1^ALVNapZEQnxiR*U;jj z7`kt@*vx$=Bw^mjB~%9MQH&c>V^};yjdu?*0nSlh1Fu=d>D|UIQH@!c!%?*rNZv&B zy!;X2RJu8(+Dq>~`r76_5h7bGtw!-SnDmdq@8J)K?{9-pzd2gd)5BA}8c~(V$3jCZ zdT6X;o%C-mi+_37EG9M#*oY5e2;b!Ws>p99(_Q=(5eHEbKat@zG60#HnABJlIVu=% zEQ^{Qq$jjl(T))c>W{;%3JnIJ!X*R)x=8zG#KtJ$Ro!XLXaTCvXs;;*$tH|llHgW< zOJ?{Mgz>$=--ILl*KmpZjYDs3wj2v7`VEULfxIcR2RZIwd^J-va}**2UbLse@@4cQfm9L;cA%C1UBYmDGX>ib5gO2;c&rYkrX^x zh`rA3j-hcSE`X5@xp0gC0%hlR;w@0^7@XX|Py5H_u`kzB4v&YW*D30A8V7vv&k~8K-VKI7vsJ5=8P&_yU$K1LF;6RnaF5*Iw zvFCLLX#Up~es>fgq7E6rYb>5bfR7HoLa@fWcmevosU$=YBkXe-05=<)vEu1}?;MJb z&;)G){aT2x4xz#_E8WE=q4G2|h5NoQ$9S*p)zgJ-2xTxNOAD$^D10~HE_RCZXr{vR z0!hpTmF1N&2cky}i?&mRZMZv6-@Pf4XafeGwjjd(g(Lx)yA2h|*QX7HD!n^54we4aBpFuGI6enUL(I$xj#o;YCEDr>ak zB9ok|f#3~r+CqNw_g$v5j)jbvxXe*Zy4+WGU#1g9_^3XJ(K#QCh&y( z51*oopYrVkuU>xI+v~V-kzPJ!)0^Z7BzeknA}kGSGs<9zvJLeD2FCD6d~0DMp6?PL z#z74H@g|Z936gzGVbN~0Bu?B)IXu7~u{?NsRZ0TWcLY+!+fq$K%4(Sr0%2WYE_ne9 z&c()MPt=M?GjStAM6`q228Lo!XE;Wx9rC(ABh{<0i^XSyAG9x>zEG%kT;v7BWElaM zdJ(C*;b8E$2ty!F>iA$Y>M2B*_LTs=Jj!NL8~1`24dDYqM#$T=LIOR?2Ez98W|{Zy zcEL>3;jUw&ndwz$ytu`ne2%GMOZU?p9yyA`V)gHe@S|7*QnV!N``Tn-`fh#rT#DT+ z(fS~nvgy=3rk^VKdsb$MG1MSJ13oJ=s0<7Bhq3AhAp*&`y)J zirJ; zk-h$Bu<5ZK_vI;YQ)S&76JDSwwLRdY@R*@GwYLe-=wEBTU`OMzC<;*r!Eh>H2tQ2v zjw^W{60;4jtcl;CZ=69sNX{g1VV*lSQmy}uhM42-nra8$Y;X}G9Yy)Og?qL+E$frZ zYv-)0Zhe}=UQ25hX)ccyeUJ$};$L7A7$>jYZMJLIh~c9Ik?0m|2pBKPO7U`YV*m0@ z;rk_67X)xJp0AgtVBy5p<|=v++-Se(g~KFJ@Z?v8dqxNvEQxtKN?bZ*9>fwcela(; zfeN;0t*#ept9(_iJ|f^ZVvO6Oc54cr8B|5_yu<58$i7$I2!8C^L#qtM!!{X;?*~fU3d7f^gz*}0sFz76ySRG05?XDG6x~Ecj zUO}drNmf^9mq%s^y4!$@ z!t+fj0<{)`XL@}?pK1KUs5e5Ne-8BqP^Qo?_V9>xO=l47$BPKJf4ehM5 z`nH%a|LRQWEav9y#I}`5C@l*-bht^JPGEb3aCQ?3_`iP%@Tj9q_fZo~PM#2&$-J@Z ze>PeHxm){xxZ92& zjJy5*%iZMv7kB&g-`tJ$e{r|d|Ke^`uK>|%Y1W*wl4`NNOrM=t;ECnv%mSzOSV^-_ zJ-@^6#IwUMwACCa#@i=7yk>7UP2z- zr zpB++q->EX1(HoG-AV%l%@~EUUyv5bTpl_tO0Dk+!I}N^(4F%fMQ}uNRJySOf)P77T zju`i&SQcFGtKt6taNYWUvn@plb0u=CHrOGAAr<+)& zYqL-GGsxoh@W(c(aU{)FvOEH=47t#LH+ zr;~Jw@5>|H_2vZt#bu%oEUN7n1pp4D%yZj3z%w|IdR5nUmEh>{N#x;LIgoY&NIuK! zqc5E?A{oEMeP*B-@^ISdQG_5EPKQ|m^8V}49$0Wyjk&xcr3Npc!Q7BOeHs`O2TDNt zRAEdf1iB8qrIz=9sO(l;7tDmbTrhS473a+%-(8g3_U0*yGT#G-G&BT0mx6$g$} zUqbWuWAfYiBtf1eJos=7XXdOtTIvn4iNtUfzTp6EL=Y+n^)!GEkO;5r$^X~eu$N}6 zRSvA}N%`&i%_Y<0=c?^EutKmW`Ja4p3}{Nym=>t>@1h!Ig1@E2>cvc{@CuSF1ohHFVH z?EXp-^Ax@kssvR*13creTwf?NK1&ftm((GHWCWpTTH*N&Z+=*OgZpnSf(Q;xo#9hi zvbXn84$I7UqI5|!Qkrl=lmAnS2q@5Bv{Lg&&w6eVkfO{1MlawoN>W%~BP@Bx!d>%_ zqLOC+#5Dd@rTedaRt3TM4p&@(k{=x4NRq&}<-LcKLM#zfjRPE9sIfszk-xQE-g!s3G0~$ch z007dpLUfWLKx-Mm@yDdI<_eZSx8Xx9oFhBWC(z(4Q1fHH6ihl0$NDFpBq;$%;6MWtgXIan;@fP?PLoKKYTRxi)gDj3(1w@~gR{Jv2?4-jHW$oFgD z)ZA4dl@)9V}Iw>~XwzXI}^*Aq_Iyo~xk9aE- zjiws=m;t^KU2XubHvhcHh~xZO(>NxY&g_^=|(~BtSpz=qor3 zFh&k%-?`=E!F})f4)6g{{4YFI5&KIrAY#(&S^FxWLVp)T#hmtUye6p(qjzx8?|X9u zhHsT+K2Zp5bW5?hnMj=TMx}h=YeAn5V!`bh?0)Ii){cZsR4_HCZEd||Q|sbqi-{41 z?Ls{Ho-o_VTCJQh+*0n(TsH^yhqa%wpKQO8i7=uQigI_h`{0vcAj~B37-L!#bfZD- zj5-0x09<4?Y=l@jN&np#_-9Rn1@srEQ7V5m4ftnGqx!F=@$mnpX|(>+G!mLeQE0{g z=VWB~Une8Kp6>9rHY%!WYQe&j=srH69xuksv@=L2H5vo)lN2h6pJ5Ql6A^ZrY<=X{ zkOr{l^@1hzc0{XEBZxC3+E;_%WwYH zF)85{6EO{PoWB4gt?Y(Cv{{@Y6~0pmeFuU>ToB_xloP0<52ho~ z&XXvq`+2(g;mxM*N5*)}VDI?6aK>u0-&m;TgW7Su1R;pAVZFiy;Ju-4)MEX*k96Um zC{n`;QieaT zMnP5JgjyHuoahvf(2=mAMvlVIUq%BR+kRa|Yu|?Z*-Q%S3xkgRGh-3TY=%foVU!+F zh66h+y8+KA!|A!Zv zhp*x#ff)5edx@d)5=0i;cUfK=Pz&XZt9`&$`234%qH&%z<*viE{f>zNofL!18R0f(F*I_w~csWFc_m zc8UDTOh!;TNi~!1-v+hN|5;kv9T{JodGil^eB(&@MePI z_ummPotpAB6Os_{VdBLLMEd`T7exO{yrBClUSz}0lyrG|ISMox5m^8y2io)T<)MNG zunB;jCjazsn*bPNNL?$rIvEJb4nfT!<*tT8`-z`mqRpjI3WRhz1OYl02`od)e6)oL zc%e_#Oe8h|S%L_Jggh_cgy#vSJ^;$|+0HTnoJP+~fd+=(d8jZ^Ni8!P6Z8zC4sE<6 z9T<0Z|M)(yN)LhKL!1x#t<;xQ$WK<|$m19FEsd+o!YyB9*Xlff0(aff z8={zQjnMQGp*I8e6VRJxZ@pQeG$Z|vn|Vp77@?K@olD=(Cf9_99KuVW;l0_xSiHqw zz8X_3_M~NcFoxbCR{X>rk)@~W*(P>kr{2w*Am^l(dfRsQ=LAUn?(vY_kAChH-SdDc zyP`^Zb3@E?B#n9Tx7Mv{cV)xp;hY-k|J~Ba{FSwDNpC(p_GUOQBr-$3GHLyM10lSxh%DB*^qrj>R*+mF9vmbp}g7yl=+3bJyXpkfZi0OkElb_N3Ef%ptl1Fo; z!)#_)mMbmfim0tBuS^uy+B6Cl$B0e(ZHdb0Y|Ji&r@#K6{x0;^=-Vw!6jwQOfz80_ zL{yRkYpYfW&eXzy(LI%e1W?GQvsEBnv-fvfZ~~K`T|>99Us&4>_1_i~)@ec`@qpD& z)C9n7JrC?|?&7VI-=YwZ7oA^-j>sV`1agf6d{e)w@U3kpB%T49BcD}m2)HXl= z==!F*5~yAD;b`n2h)3Fa_{8(`!=_X#tf_!CWL0AJTan~LvcCYo(|G>^Cu>W4!Ij(0po$dtvrBxqP}c;4mQI>6v?6Yqid09UQgdZqEaU2p(LHmT?}m z%z%UX2z?Ak(BId6B0kJw03ARn*-5tgw|QHK9xP#B zz|p{~6T-z0By!Tj3p*gTi}=7wgB0+Lo6{ZvS}Z zkfsWCjaUXQpl01UrO^Jt^8{Ta7QU{A(6PB@Hcu#yA5Qlb5=m}K-3!(=Pqlvx`meV9 zmqDMp6oT;3VB6X8z>Dm3)lR{Q?R-#s5^NI#Jd2;)97x8jJpZKV*M+cdAp}Z%(Z;L? z=QP+*ke;*)9VdXwbD)4=`x)^T@NZx7Z$Bf1?PnZF6$`Na3_-FnUNylIR>}`%lz=0n z=%;dqAX)4OvC5}vLXYzriRT8OPt}+M$?v~>Gh{EWZmD8@1Fh!914IIF;c(#$KO(k2 zcNbE~;xM#7fBl*h>Gmh>x5%h?RTV>YOV3z%!WVF5(y(Ivx@_g=B}j|V&}*8Un3W7y zcQ^V3kwLaJe6uX=2uvK2CC&a=^}!iX**uuR5@00`Q(3)?v|K61G7vauW#IooSq?sq zEpwlg=2OK;Rjh1>Ddnl{*H)VU+=wK78(A2OBALSQ8m$LPwTSC+eJzeS*jW5UA1#{hrREUG2JqWQ1mOYpfS(`43V=J2kVZVoq#~eh6QqXSN#dUR_z6-s(Drz}+f!{> zZ(p}q=uqdUbDv~!!Ci6gdZnvLOhxg#wPDVcd99-(REZGw6dER;yJOX&5`S|!^}uEp z)AHEtv;*UWNYniM;wbQ4T}|W3o}X)T^S+_jz8~zS+K7aEE$rzbHWaEtUbgZwL-66z z$UIZfdA9IsLcNE{?SH(3Si6R}FI$^6}7z83yL%F&rG~x33MS^p0n=_jkOU zoxKx&p-3}%uUQ}tiEg-Dl)7sZ*UX+W4z=4@9~(Fl?{0)YRNkNq+E;uP(s5|2*ht2z z^kc0ZT6Y_0;7uKy3t)VUz2rU2T?zC(@uICH=*Kt@_)WtN8_wkoFLE$e*hch})Aw`> z>f>Lkf5UHa+YhW?v4d2cuPoee?yR^tjEG65FEz2(q`5MagsSPz{HYQoe4}ech|Zto zq#bzuHY9vXofK{hASkY6cbSgjEcNZ;r4@8}KlA#dqhr;4Q`FDi6GdD~=6U&piZcrK z-XjYl@i-B{_;5)!M$Y$GP0m!qm-z$yq?_|oDtf7w{XJULl4i<&^_~-t9?%Z=3Ct4V zOB#*oz-})q$?0&7bT(uI39WGr*jk|#JRZBHEle7#AH0hT?bCHbT3p{RG>s;n@QfBH zr|HfazA^e;H7jz25NKNxI1u5qK^_Zej*gzpt=SU@S9)_`aXe{K9KmcYM)k5pi76sJOa;T8Gkm2>1ivYr7*i#tKewt^~e;vxu((QuHJi#*{7qtD%N36Fx)U z-tIArvdvuQivC==?f)8IgGlZ9GVs99VOSbb^n6CTprUN*kLoR|Xh4j0?(QST&BX2a zyxz@B8t}=}@w9ch(aHjP^~23m14l2W+^D*M@+ct*%C-aPp$^=e(vV4Nq?>9B$ylrx zsXM-Yh{YaJboL@Xp9@-7JU?AndAuOaAEwMd+a11~zr-8ejE2SEu}$ToTcBq}4q^XEKj!={bLa>9Q~PksG{shhY^lEbtwM zne!Mfi&;kcT|aUTdh#1)?kJfvIVv1P!w&ELZqy9*soTIlfekbjDH|K}u`x$X!m6a# z$?s^m!Z0=h8vG1NKbLD6>SdiQOlUq7O)K$JB_%fI?m)h4e05Vf>5g$fW_4QnmG=j! zabx@IdOEj{KxhOrOj`Ud#*1W+y+Xc}K4VJMU`15!`l(h+%H%Lsr_eNaXpuLnzGV*i zs*kA*&4$ZLaj^C^8SGVOY88`|<}9Wm{qzmX?-DAVk@?6*n}H(yW4G<#mTP0~IU6f) zo!Ly|Pej)NEymnYWfkEK=n2IQ-_OH^Byo^1AmWpy`yRQ=s{`{6G3;th7`t=EX6pLFiZgYalEiLBn9;PMh zO#LUC-Us2;4eNX|?ep@lBv`|^)qP>~l|#yu;0^3Gd@E0EBh?n0=7QOl?nv2Fe!sbA zow;53me(;tW9!NZ<<3Cu)BKXZKFzFx^T5jV(4pjOGpv3jXV@pi_suhiMrtJ|cs`_> zSHiFEs@{kGh;U}&<-{?X{e$9j>;P}nPJD{w183BpWO4frsD%Vu1FAdu7CL@FF&0?ldN^ig4>?L{*6EPnvIT+`Gah3+p|p|?o0P_w8n^pYm&y2%+&pD>y#bJgn z8`uO?vQE3>8G&uf?nNal((5E-mJNh;EoWcEhj3rj6F(V*qQi_!>?NjuXR77%H+s*$ z*yHQc!rKJ}NI{rQbp-(Ie>bcBk}_N6 z`e)+ef1Bv>$~RMA%34nD=;VIo%3uF+Yq2>>Kd@XiceRW1Ul13Xz3gVRjy--n`*f=$ zI`iT=Pk(pM#ve3?am$vhNaYwQjUEP}va_gdE0(@F0ebjXdOueMnb8cH|GdH9jQ!DO zqu8%e^sBF5!won~-^f6#nR3n_!*k-`Lo+-3aLJ}zVcD_wrq{C>uSB0oTtN`!7@1hF<&pXe)(x z#!AZ>9LNu^j{Sx|FntM2Q(&9m{CbSq0W@&{<)3l$&0dYoUIy4I$*MU2M+MTgHf(5Y zmTD={9(Fno2C<%vMp1u#$9!1=9W7?XF&e39YiXYqfmJ(aG`D?GyD|OW!iOzK;7r8!cRBBDY3H+}g5?S}&hPq0 zzBIFthvwNk+TMp#9C(;DVzf!WuttBDYGEM!wXMHa+&sC~(9_3vZji$U3yUJ7Zzi**J_L+Qwb<21LYX9%Xva@V(EX@k7u*OvIITe=!U{r6t$m1U>LGX z?b~fhHS9-#SiACb=K4vU{uhnXUTdHHzgQIU=&%Km42(tnfw8E_JQW4(w2TOu%|X^0 zQ68o;ybx^_Dys!|(Qlu%{CRCYkbS5TRXXa!{sZEr89}xB4|~b}tW6H4WUjCm6xfqx zge5MI6 zV|G*n$@~h+S)IH-LwD1YjW-e^)T=SY7B~lIIwHgPB@9ytSK=bI6E0K-o-5)pGz{((>JGG+}H{vGdKw(**Y9d_jb?8@Eic9G0xX_u}w zD%gd^uo8A0BUtLvPw1Uz7D4}x%{xj@!{H!#HxWbO1MW+k2u8~TdPvFrPGoikcZl;i>L8PKV!kw{AwfkB@Q3g?Ho*%mCwiMWCPG_FBo8(keCR$F-V+Kx z={)ccs!3^fPTK!@;NR_Ir4J%dS+Q;7-qjKvzceM_MN&-4D}Et~9%-;4t~O4{dlgwA zeUtD-<%K+twfGG#p+G4sshV-Z0}?T%4C$t6ugn#GM*OU(%qckNc$?AVdf6ugk+vgC zh9}L+w5C_iDMCb@MnVP`zs&0u?Tl|(zcm5-174|$?miXUlk)H{AoEGQ%SZ-ZnvfL*J(k$BO18H7$E>Vmx#Td}5(2cJ=0DgJ*tXhbZ zFF}4VzQ@RhvwZE(KbN1TeVmGLng2fluEQ@;EIx`Sac7^7&WM2*M;QMM7yWZ(nmyeb(Sqzv6kr?{>vHStAKZ3C>VLEQGq2XP zYPpe;$-!IlE8h6T`sHQ63vTYB7og`%eom|G1zj~PUn?R&`9{Ym{CJtUR6OdhHebNP zu{+ha|Fz%d6EiG*($IKUV8`pWeUK?)UCh8>S3r$WvPy4J)|d41#I0%7_U%T zZN0k%I}v=Xm)2W_8DYmXu6}aB=av%{7>E396_$7@iRGTW9@=z)I;>*QxU72WO-2T- zYDVn%Y7_+B$Nv;1*0p&`@WKDTDu#C-XS`uesvqC~#Vz1h+(}6soE^_x5OC4sj${ z;SRmxVQ=g~6yU+dL_`oVU2{eH(!_bwxyAwY)d-*Jak`VtR=Q&KxUadkiz;+w6h3BZ zad7cVC!}8ahLIF)V8y-H`iE=K6io_ik17D( zf{)P*or3?HhPx`+pnmyVvplbGr9lE}29|MIG&C+|iP;EL_7s(GGz_R{?*g{vPS?jN zDQr&|v>XEV<}`8^^-xe8+KSt*Ru;!lP-clMj+OV?_MtNlV_JgzBHP47K1m{gNt zDK}=VnvAo6CN;M{1`|yB*IsuoW|v1>ahl@xb_2nQGZ~S{s`7i-O@ZL-W@on>Z8yLD zb?fX#_^Jp6Ab#tD4fu6!yY8kBzK9)16T07}!0?MynXQv>qUmaQKnl!@0G8N8p ztWKY{ZU1y@8C)QSptqz*JfHX0SN)S0Us@Um!>h$_QwCItdcCqK2A_csG(R2m7I&%J zL?e!FuRkHs1@?cA7`}Fv`=Ws=hbVqc_-%sa1=bI@DPwS_o%Z|vD2qUcHS*h#A0Lej z-ZYxC{8VxtVgH;la?7e=z+Q}5{rk79P2Y$jxAqNE8Ny-v+bV3>V9g!kM#AswH;e{^ zJwT|@8z0ul$<5=AJ`CqFWcpQS1l9gM+=ztBcsg?``651;&9bjUr!eD^`1_ZOho6ms z(ucaL8il2+r1p&g#(@o4WIyLHqiIzu5Wl-P(8j*W<8z+*6Bb;LD`*b^2h*VuqJ7{h zmFa5i9vRM-VZo2kC;mVsI`b(sWjR@3__B_c^tIc~hDE{_d@!r!Pbe<0V5uP4WZt<{ z+>C)s&M?;g*E9#;@vW1St5Aiw!2CKmD8C0hFPfs#6oXmIkd{Ee%fYIt7pXx^KV*$~ z!@~oK2JLkxKgMSiIm8eRy8@*P0FEZE*QHURvaT8!M9^qAshjOV1u_rRo7Gp zx`5W%Qit#^?`--WzToVcze~MQY8v%oOm3@#(3zeic6!=?AjGj**tCddoPrkx=9yy)!8{A8%TGk^e0C8IS6C;^ma~(8MY>-PZvPqOn!u2GPpatKXp!ohqN-=@@q%FZDA7M|dQGb`$ zg0m-|>fHtr)!=h0)+hu}M$3tJ$Cc~x=M=5T(5vFGnM&u$9sK65kd0G{ZRmcLKB{|? zb7c8mZ7^B78~t_YU%!HpIbe#r?h}d^4-!d%Q0S?7AeqxW@WythWH+5xK~9BkHwY*FVe@HSG1J9;X9E>B?PurF z%L+pkcSSh!82ONPLD*WQ{w{~@f|rx!h+ouFL+FkKk~F$TFZn4o-A2NARg=&8DZxIc zrtVE*&)K3!_9gKZ+-!%NBc*=5X2ttA%W}L)4miC$TKiZX5y_Jwvbrl9VWTMAUkYu$ zJM0&_$3)VLp1($e)l&_Rw71PnehGn1UNcL!;9nv=Bj>W0uNQn+zz2&WniYGdVp787 z=Vp40cfV$a6KCb!&$gR!s)XiYaePivm`|ID5n&ectry8{KrO)B zh6vmibbkTd2f3^*DU436OsC-~HajfB-u{gs(SJuUqRNHYpl1==V*3cI{goYO?aa?} zWA|oQMJU1jXC0yTf3X`I6O{K^Pp6H?kQmxpq84>j0?s`qJK0wW1Y{gTkpAXMA6XPH zVzHi0@`F;xvquqjAJ_C}UhS~n+*MH8)yC)^f7-0ZE+vrqAX4g46T!OIZPE4a1$kyi zebPfGSm?N0V_Ziuqs|M(I8f9_qi0j>wjnYx8`Qf(|8cYIi-lygT*Z#&&z5)QKnN+B z!Al+f26-fvceroODpuAwcRz35v=tHCVs=PXIkEEpX%cD^sQE}gh06CX+NQL3-}1;+ z51i1U1HmXItd%#uEY8V5*Y&t&#@ZmU(0X04tmw761D`ydXr;E&3S5Pn8h)(97!3G1 zq^jwph=lyhL`VBYqJhm2)w0$ik}gLl3uz44zc)p3tcYydI%G6yAF(#-C0|#xyla4$ z6T-I<@J{G&3eG1I@vq-DmA8^dd|uAiWlORm3%>jMject{l%l#fUw2;7maO+4p)`|l zf6$67eJZ$GlR$Oj>%?l?kr$j07OYVJlh)j*U$i$>;dV<_bRGKz)3DyZ&XwC*lBO1L z_x8dE-rqNTx~rUYcYt}_o@ti?PrYtkXj)g>U#Pj9byq5jvgZ%4>W|5gFCXk1HaVQf zkRhNpBQoIfmdjyL%QBTXSg|mp?&|?_QX^iAL<;Qw4;-*XI=fBhSXu?BX{k{S@w%aA zVr$mtO;AYufB1UKu(+0{VHejR!3GT^xI2X4B)A2)!GpWILj+0CfgpikK?fata0wdR zAxH)Y7To15vY-7v?{~g)oj<_*Sk$Vn>gwvMyL;+8FWC-cLOgM=BJLpL*<~KmzRn}; ze;c`@+4olg&pk0Q7exD~GU#4-lb)PGuCLF}TN=54b#$CYS<1@XtKQcK2eb|#erN@2 zNPnC|4P7rQv?2M(K2K@0e;M@2S$k0dvyW)=+&HQN9={Iy{@5LC_%YfcN@pbA^8Ph2 zcM_qS0GOoiJxOe>KT;@oY4eyo>OtB-SM!9^4^(6mlHqHTJt-?Tgn; zKmZ7cv%*PU`{OXYjvY-6B|#wSo)RzuKg%h0~%4QOET#ESk(?XDEb5&5U|(zS?qMhTvYgJ@E5R3mj=y(4;r+9md8Hk-z0cR@Z$ z-unC_vECpJ^@*I#`p+dJf_uyBMIGCB+7#{z9~j=z`bZ{S_4kb7$lGn+^z>W??<2o< z*6q^H$;{RKp7PpxU&Bam=#ht)1#Ey$Z)18=(&XB_QA^)Bj>Beqs92VL_bcWgpgITU zAe>=P+6=aOe)uLVPLMedCCQ%=B_^rnjYb%S^X{WCyr1sKVSCqfm4n=tD91ser|7sU z@l92)$iuH2q|I($VuwkaaVwf_sUDBHuj5U;N7NQy$hn{nYMk#Gmi~&((jxn#8gR`` z%fW2c=vAFrgv*|?wxt?@0Q7}KAWRYwbd~c5*S^*5z3EAh!wjm@b&7D$3&+YVe;D!^ zn<<|Z-X1PthQTQ^G9XfB%Qz~DyC0clj7|#wmEHEGy;d~4TK9Y|V50EeLf`oL z7eT(dn7S-L5AL#7`3$$CZ6T+)Rr@IFHwcrc6*d=< zd)2e27SCdP|70eU&qCR#^RnyH@Vs-KR4FVsWmV1r+o(eq7$e~4lX-iqr8irPC3tP0 zh;9VOGhf}&}y)0owvjdNifR{shgmit1 z^w!|sL9TJk+wPTnbh$P1=CiJ3f<%6`%vU3`v_6OC16$m*3^NRUKaUfYoAoN5ibyhU zj>sB=!uevFwWZyGm*|&v=T>w-&~v&75*s!pHcZk8(UC-$j$`O}?Lq03$TM0g_K3vo zG!`&Jf^Jbl$`jfgvlL$D_o}WTSKfU^Ye%X~MdR@-@KbE8dTTf7ZtY9_rQ~WH1EMug zsx{nD)xH*F*Ix*Nh(;l%2i&z4dMo{j|1-1!ipi+$_FaGS&_YR#l6XVPt@{x~L3SU* z>v)3pe*=#}2<~1igttxinL;ImWhkw9Ir09Zcz`0(OVjjE$y6?Bavb0M5Xa+BIBY)f zj=%p6G0NrMdw*fpD8x~(2Fdf<82`jrNzE}MnGF0q-!Z^9`da=O(MwsVF{l%gF%pKT z4svQtuQKW*f?O}*4w`>l@HhBKyu(E?JEgD0xe zC`|U;e>19`hu>)!O-`@|Wl-!XFeH)fbd{#+X0yC|jcB@#YQ1Q>mvQ6oP$nXYU)*{l z4PrSiV1{YL+CY=qJ+nL%Mf!Z1(55Am3CYpU64Blss&I1(PxHj-j}SHy4RZ2+DQ57( z|J-`LN3{OA&t$QXA1_35m;>}r7kdBDI-cS8X z{gyv_eL3B-y#Kq?cvN{{mW?;&Yr~nhSk5f0UOxnycOu$aGwV77P3I4LzbXH?6)Xbj zJuA4&xKH*yUViOdpXKOnB$?PJsaly+YWj|9Kx{00Y5Q;4r@85dL(L6&SHYVtTKcJ2mqrmAB~hiYPd(V zf_Q1+QbH+a)`eq~aR9KIOARg4^K6vqmb_Pi>cFxB4R{0UKW}y_C>L=a_hJbLm^f~n zMIn8b+8F;XfYBMr<3MP=x){L|VqmU@Y5i@%y#ly7%2dAq$pi(eKJ`cYrn8lw>Id%y z)Ecx76r;F6W)&OZ6e3yhzOaB#vZFga`2HbI$Z)AYiCd(vG|PF1@XyZ(7#DHRTd~d` zmU7s`QqH~f5^1CCt^H71DMg2Ap~uwgVg^>2$5afB`NyU|KaTiW+7eeLJ#1RPQzY+$ zlUttJF7Za%_nL{$LNOKqTg;BoE(~YNT-p#dD($S$z2_#NWxKoTF}^JeYLaOkzOX_s zvLXnztC`PyHi1@9XkVx}w^GejP#sC6wShP6JnkaFvq!FC#9wH~*0Y|-&V#3dI?-MW zdt)g4fuI7)rMp|B*oMw?jXe}yKEyJl5A#VM?ojdAdt_jLZ{WX+K6EnQ;piHrE24IN zxWC;nVSTl-l6(FQ`-Fc0BHKn^cdhR{y?Fg=0PY3%AR61am6%(~kZJ>t_=VAy5_dF0 z(mb#(fF)rC^Fjxw4O8zlV4<47JlHEh@a!!yEyn?S0Mbpm&djGn2f>U>uSE*42`|0# zXLm%pd?I<4j$;PyzoQ7Q?8{B-$xmQPK^pxZNT{#ETkS{n0K2uejOo!oTH9re^7j#> z|H2=_7%05pLtoxs^ym3uVSp-b@$SiRdg^bJ=2K)}W~3*f=e~;DFY5P=I=}E97~j9{ z^f|~1X?^);e&STnaO%^Va+&pdil5K<;Obz5Rx+{f+J3OGyoqCrdtI<)2DkV4XInxO zW{9nGl@QX4ZCa%*tQUqeiZ){g=am`MG;b9N8}l2E5Xdzlv`o3Gu=#w54T-~e#uq-z zMpqQpns2*X2oMx57BO-n_ z?^Cw@_Eaap5603%cytyIvo+<-q;3*lC`M73$f*C?B%1^7sc_#ttMECi^F6*3v~yNl zv4QI2t|V#BB(j!@K)#`51Md`wlKo`OBk)~=Fzub#5ZlJfk!nz*|ISO7Zw=1wBqt7? zO%4ru(W4*Qind1aou`DFJ1E>(Ovh~Z*YI^@co^fjCcb`SmUwSdKI3r8(`0@y0_$s&BIgPlsGfj|TUNSkJg`sJm z2p(93qo+Rhp$(fX%WABO$)-COKeSbUlBIx=-AlB&4&+nsD=ehH2bVQHFcb8ERa%UY z={>#0W^g#nNi)&Ua`Yso{zQ5*>R?4Owe_=p-wSV&W zQK?KZ5-&aFAG#4U*wx`WzYChjJ&o_2tNBtfl&M2G~|L$YA;D*vli*swpU z&@ctNPaedjA_!>#??IXEaKw1PY`8YwX-B;k*G z{F6xktOpZf97Q|t2NAULe+2RS&Noe0-Jxo1CsWW)#^h=M%o{7$xu!wd!`FEWORDErick&`M|`~!aeFH5#xyNvh+>axOcisZd)+Ot z9d;HM6fWiS+&fKi@LZaZY)bW>pCdMoIqP9uq*H6krhOvOqBO$2UGD8r=IJ5cm{YNf zyyc*KYCH-`aTL7RZm2me+_C>SceM@D5`JEx+)HBKIYho9_Q0dLRD{&!gU*`X0T|0H z?q8hY$F3lEGadA(oALH>YC>`qMT``PEcHklOqBu95wQFQ7T!sgA!mTq;d<{7HgHg; zH95UmnnQzWljVJccs9cH+eJ)Qc&>xri?v2_O7w7U>=s9!HSWVpB#!Ke|L|F~gS)l=#m%q?VpMwvecRdpgmzKj><7-e`$ z*!|AR2W7KHIXrnjibxm1<8NZa)dNr^tXV-;&iiLSAcx3-MjePgrVwzFt9$V7wWdaB zg~b8yIMB{sDxh0M!#F?=L^6G7>t67D8kP@KEd`(%i}Z@ZdZ&Gjsj{>eM!ROQ+KYUA zmNqbSc^!bc`W*#t?fREss!X*x>lCZo1kKntHA9F}S2Z*EdSL4j4a;9zm_IjQbY&cE zoO5-fyA6|o{usAFzY0gXhAps3{e?=FAD|L|MQE^jOH^wS&}fmqJ94OPtBAIWD1Vys z_`K8P8~~+YweUrC)&P0tsh& zH|o2x7UjxAf-g=U2o~!PF=tSmndH|>9M%&r|1-8;RWr5Q0dFR>mDzjsbk!zS_LCut z4G!`U;YnL(iGZ*Q@qeic+>4_}rd#_Zq2NV&cM8@c8V_}*nJ)ytaN2(M14zl^-WgL# z%gD#SN`@IgEvy zLwLqxPTf$H9sf`5UNweqb9a(W5_UG4k;ihwO-QHMeGGzG&=@kmJ^r4%t(NbX3Y;&(1N*aCBSQ%{Km&{iwB?9Zkn!BI*9v0&0ajS%KkM$P^s-B+`v z6%z3de{PquMS*)Y!GQ`303ain1a7J~5G++zL`%|-#Cc#nX54>mGi4je@9mw1s=K_{ ztM$^BY#fS{#~TosNy7cLEso+bTmr-0u+Tg#X>RoV^D(_;r08+)ZI2TF%!m;;8GGO@ zzDEaG`LpsQ9W=KcH?7PT3<*K%x; zx>$h&tU!>nbn8S5{fEV91%}Sn#VyW-7pP08rJ+4KDU;5o-DjCz-Rc?Ks5Ini4N&)& zBh7y#%-FC(SIw@naSS{ATb@M4dk^ZpFu3S zo7n_R;0BuidVFE{#B@Idh5plDG|4>tHa))dwcV#gWk3G87+$W=OpIqfPA->HP(lFn zf;>xo<0YGBCKCYwdf#=Z#5bj(jl#GKD#5k6zl@18jcL@s7)jnzq`j6L)L)04q*dwG zQ0?&bu4S@t4hrb5MO3x^$!c8=I&X047L{r3(C3*}v79rJaaV0lAlC>?uXH4NFGna3 z6-rL9YdFt7T|OWeI~x~!d)UYJRj8_jn@?4%l-)XgIE~5An1z*pa?!S4o=QIeFACyXe25g@5%OrT52 z7$%23L-I1^fFckhTYRB?pMXm;<_SPizW>598$iky^styjSoL>y_Fm;>v zR^bhEmNtVV+3`RuDq-(#-?euc;0?Ven2UCvuZ<;uB~FhtgtZe%EFN~e`#HI1wb9jq zgXz@(>Uwvu%0bI_RZ06PB^noBiA>)e_W_E%pH1MY2(^}hVOJ1`R7V(G zzP;UGPaz@f?dPeojJv^g`3My+;of_R@){LRpXjo>~CnqR6z6cFLY2! zQ)g0%mNxL*D9lo|KMf^CI4fD#2)2J7i)FJm;>O;5rGnPt(OJ;Zzn>Vtq=kj`0^Ra4#{hPa{Qg z%8#Z+HSB5b`4uMj!K_3jM^*yK>HgQXL9p>bc1T|~^50&#zPAp~Od`)$SdVuw(*M>0& zTS%xiE5AkW$8KEIJs5$MsNS#R{Tcb3prGT06>LB;A!J+YAF$rJshxSk+_GLG^@~Rm z5kE@KD4NCxQs*a|T9IImstEhKM_-8TZ!MYzw5G7FUQrrbsLyA+SkmT92NM(_+bh2u z)-UAQ!Y-D!k2jpse5pRK6!;Ir@w+XJH)ZASF{l&;FXAhuc%upSv68P+>}&Si{_?J{$Uj*xAL=tzP{6 z=c2DPj0)&@2k=bQiU#mP{;Fm;rQM}($SM`%b;z2dW5~&gFHk_9+QW$lyFZhZK#F4@ zza2GFp{Co+69?oBqNh`5!@0((%4=mB{~5^=4B(aj9m#Li+ah1wTNIM9NghXQIY$pF zFZ>BQ4zpjQ0lZy2&0g2e9>91WbOqQH;4x&wvKV#ba^N9lsR=Iq_-A2fOPD$EpWlYD zY-=*IBKDm@fU(@e3VUrTu;1%MDm{Qbfcm<;qdX8@oGHE+*Jx9<0;F21amBr;U9ky^ zMQcUg2m2`??Q7)!VkcCpg0TRoC9p#A)oVr9-U*!>TDx~X6A~Z|!}wm*Z!Dmjj|5WrW!GJFs8Ggp*UQA<2Y?2gGxj^0x?2fEFYwnfkqmpJ9_+3?xV5{k z%^#}+=H{GNH`-+4akeS5)&|ML2H%3Tn-w+V;K+bqifY6WDPqgo8U3|g$zR(c8Rq`A zT}GBi4h3VGP%H?m){-{#tVa4wvejU5*D+XZ>aVBP9u7POH`b)$vXlQ`vMsfVwGT^N z0-A0GH0B7olB+dwG{~nqQ!!0m?=k;3P&x@r0ro~7W#h7Kek8zNJla#fPSJX^jXEl^ z+u{pCcJi({PW6{W`>Sc39{wuVxC2*Q54HN=x$%Fv=z8vUivWd&>QN}0bvaRP0}QiM zdJwmS2Eec8BZ`>{Iv$}Y;AuuomOvgN#NnJfLH3%#lz|=Y7g^Wc4^V?jO>HMbcTQm{OlzvKDWeHLs>%vs7&a&*L<%&={fv1gh|u>=4Gx6~g`)b#KR4;$wtN+l1IhkpMGwcN5;l(DYu z`z|$m_f?B zy4>~eCKFPH{^YN6AU%uvkV#XE-@$72tT8o30L}$w73Up(eI4C0cj)TawU;VZzP?vVnQ{F(dcs< z0-I|7aKl5FDE_-u5q~;so(V-6(Fl3|jkKz;VF7CMvp#5Lv414B#%km?`P)yR+sc*d zQKlwayUeYl>mI%E4!-o&ELW;6QtZE;6@3>- zMX}^uQluTNosOldJyJqg^QncOvUy_?SKtX!oYeZuv7*nOlER}{0w}0Qs>7IR;BtUkKD_R({tU-ZqOf}zTwA~Vywvn=8fOzo zy7h#A!--AQ#%78(!pL`kr!Bdc{R=n%-~o0AevJ{}TLn6{Ynhh#Y=fun(;Jzf8ly9+ zwH10(SEyA;)L-8cd7K3w?;*;=6V&$x{e?#UmveQf5e=*MR1#ax{+8k?L7ymWaBK3o zUc{ZfMf)#J!Hgyv3(yoII|Q2?g0=0r;hSes1ev0Yratr|!yD!TR24@5ND`3_iGmbL=SLI~pq`?MI>_T-+?EU4kI+ca*;0V9agg-4~*v|6n)3 zuk%Q=cWwZkqIY`oo^b^Gocl4l`$)_JH0Wp#!MFK>wjwyfV+G8$IeD>bg*-N{`O$vt zFSX%Bw#lQWhqKEk^Ag3|P*+^-TP{^)I9jyGq=C)~ADy_QbGymz|HovRTK81W%o`%_ z!TFU5vQmM1-~A5quUT0f`~hkMuou9Ay#vphMwW1Ts^qx;f=n3&B9A6g)|#dgo{qe5 zRuS=f_h;mV=ohBdkG6`W9@ptwz87E0cJ6Ps(0B0vLyrN!pl5IN-$^$~erhaHPUEl~ z;gg9eRh_xN?8h7m`g!Sg^!pmc!*t}V71^U5{A9zHA@T(xOvCvHqd@)Fbmi8jxx~WY zXD5or&BoAXZ!+bIx3^|(eKu{sl<3E7bS{iG`Z-CRX}f?YkpU@dr#rg%Bs09 zf~y;2)hw#=dBHK;j04UN{#%tZ@+z94&%v$WRW_>l^Etb zwgOGh#l+g2!5nuJIL#eymSck?ZJ`eCxV(g|Sp3G~*SJ$^ldHFC|Y+nQ)9yn{ue zK{((qr}a}3W)9aJA2_El>H>Qpbjb9PoT{l9b)lSQxU)z2M(g)zZRvVYn|qCCjk+$( z7!`9#?Rq8*iYv+o?ifw`aoqc7J+GZr5-!r0UzJh<_X5q+1s+Pd4M!rkhE^zQ0cX!r z&o82%O?=lr?LlUiqf!3VbwJ^;g~$ZnLDM*<1t|t7=_@L=J2pM zjwin+C)ud!tZQCcI_hiFbwYn>_W8n)R8jNd$l5;746LQ==${IbZ84#TJ0u-2qB@YG z{w)JtC^=2M4BQJAv40z(;kszhy|8u&vu8idHK8GSJ^YD*HP9{Oz$%XA;%@yT=t8Dx zhG}&Wt=HDP2m`Pg%{|@7%!pPi(zEoFx3=LL zoZ4Ss1?-`Z#go=4zVfK#4X1ir9M3bK&uT6}?zA3z_RXno{tZiHNGev6%xB@A_DCKX zR}+?j<%`g2;qPP%bjiF#EcA?8I4r0G_EHABv#0}sQgfGHrIOoGd0Xl>t6n_2St+)a zCqfqc(O*Ivk5Ci_z+cL4wuTs^Vq~MHn?nw#aXp?YIwlPk7gl6&)2f7vmhb%@p?W4HiXDT#sNAvwGu5wsZ}-q!;u-1`58O3$4K1)y zTaYR!SAfw%;%-|Jyw)0}hP3XVZFBZu3w7W28m%ivo83)z=?&N#?B4eJm=HcvG^2XO zBVkawY*sE$=s2GY9AnsE?EvG#FylNnbBRu27WCM@$i|J289s3RR@Vs}rXh@huU;%; zdctKBXPCY!ztTC$?|Ak~YNnPoj4Nl*)YCXJLW9}U`0#j@M@(|xq-`(jWKc(c8X{Fi z9bK@d6m4;r+&iO-tLvB*ZOqN`cCkV=Hw|AvIPuvjE>VU6BLl_Mk=; zYVUdRnd6j!s>R`sHzG{R7-=N^H>_zc`BP=HWtHNz3TMLAokH*5grmJ}R5M(GFsKOG zDWyGPT3|@R`xbIROsDL`E+OuI#-RcMnQO8fTYt>Ye|w1NtU=Fl{E3+i?|MGR^i>5z zu8AG(qs*1mTs~_UU(OJFGJLP`u5=*|Cu;bf{Kh^dvn;!@*w?R;toqaik5w^>)rF`p zpRU$6D(G0ooSbrjE~TID9I+0w6-W>BqeG|$Eadl_hAOY`?UL2J5yt7W3dV!j`NKt19d=zc!+S7DihA?Eyq(ZdbP+m!%Gw;nU z#Ji=rkO*_1$7ae3S4u7nU%Y?rAsaO&&_{8u;?*Tj_9n_ydF`Ut#qYorXUCE2$6sSR zA{zn!0f^1CRf6(*zf%tNTrj;3UB>j-lJid(;bW=R-FuE-uk8K5ax(-u#kYcOxT4Ip zjka*FN~BGzED5fTluY%B#?^faA`}LUk4N?4&5t(GXDppASD?nf0-O??eQN{ZRV?3e zd{i@t_QRWRJV6lpqfu}%QX*ffBn%!qI#ew)OzLmWmG9-0*r<{d#n;^q=9vLzFjv~3 zk%zfp?r^+5TUkDXbxT4T!m!kWcy~cUq3oqvMJnT&*ZHlo!jR+#4I`hYMon68`9~_b z80Y{Wl6pjVjB1ezA6&J{3z!shq45$OQ3u6IuLqKY!KqtNQAYtep7ZBe5Mp&A6#Az5 zs!CgKC+#E^mXQxrh2L>X+nj;k#Ox;;EJrOg>zzo9Xwk>4z&QgmK|&7i-7w&St0AXt zy*{rA`G^?n#{GeLqRN!0a-FaA1?BdA%@*@~-M~q5`<($?CmJaus{Hc9@KdfMi+0@k zbOA;n`d@x6kGXTHXa8niA27K?Yn&`Lo>DJZ#4c}KOqi&ddMaluqo7sP6ny{A?<SlOqD-YfzswswWF|M}yh>f`NP!iQ*X1(jlxL16bVG{0 z`+Kb%6)pU#`cLnxB+PcGUxKM3Ih9aHBF!;gf=8$GS-*1H4iY%wb$T;z=Y8VJ6?Z?J zJe`wIx-i|houLtq4XbdBEp-$ysTAb_Vm^Z}|aXIUR98513P)OuD2WMdoUTiCwr+`joO zh<;<)O-@x(;vWBk`9r52C_sTz2;GRVTY+w}q+2M+MHU;!jbN9$5I#)1uR>p;$ zl6xR~nJ&R0Y1Rrg7b_2?r=cCHs1>rt=VG3EEUo>b3;Sq}Qo=rO>1 z;Gc_Z-xJD_7!q|7(zeJZ^unZ6S^6*@RS6>sdG`{2r*% zdl8?%YXz@Lt%|LZap6t(k(iBHC0oasix~^WwtU{lDK`r&6F@^3kpO=?EWK&rIq3vG zOaCVQ%||o0rMjNfL+=D_F$MGr(07Wfs^J%x@}l1>zpwjOyHU;YN^Oe!L}8@UwlR!k ztAG-n#;&RI`&Z9z0_S!Zn-YXhrNqSKN(>qe*;sbK0E~y%iX~6aq)IykV?eCSFLc-DjQZMjoqs8q4`%p- zbqxx%zwYQ~d}k+sP`?9SiDyqt;oUpF^BkASUe39F&Vl6Vxj` z#S|Y8Wf(rzZT|iydU&1kQi>-`G$!K}kKmI4Mt#f2D8Sl|h7sn+R=EiRN!R=o{avfv zJrf<*y(H*|zdwNejMdwZ-)W4p7d<7adN5|72!*jp=!3CPyb^`->km)%0pD3f_wn22 zl272D5yu9SRT`l|qL~%LSzSuJ4J>O-pt+_<9l?O;4+#}`&&Vyqm_91R%+dUF zz|>5FXbAQ?VDvdbI&NOF>rY;ILN}*;4!iI}S~2)aP6-@u4I%x&H3*ji^}>a)jbEfE ztq>FYVBbNpbhFj#!=Cexkz8+$aR=wx{{rzea^mhm2SH7&aXbb{{ z>N!7Tr>Wz`$t*M}Gu$L51DL)vSy|~;zI#F;cW5mwks#hNxPP9Ac}!F(5_dc90ZNNl z=!{jKpMaxabKc5Y(<{63MU_W}`)<$KxosITdwfp|THf!hQe#r+yxNw*YymE-v!fel{8J9!P^U=pX3qA7~xopK;=G@jd6!c0hR zE}{KN**wVCPa;?{YH>OqV{!V!Cvl&$)A+7w&_Kxm#is+frfD%{-nwAl-WyXsqB6*n zS}6V2q;YFtnXEg79;$tz@S*hTMG3!~+s;Uu%udk&KX-RZOCFY+9|xBjidq55BQ1)j zz{CKq2}z}0OJ~-hk>%#n1J5mlH&pke;M20Y>6o@TFmb603id1;o#Y1398bb!-w#^4 z9ZIqEmc;{rgib^QV5V-J;s&m(wk2rh+ogRC%h4wgWoTwx2Uui72|Yf)@zp7Mv-JjF zq^s^OODYJx*<_`4jp?(ptVbZHy*!ZofQA4q5Prw&QYWRvF9=hz#j~q;)7bm|CQYEz zUNGcH-@FQC=yUCk zaY#lAws`ZN1cFIRPv(tEg(Anw932W!`6~O4CK3W&w`{4L-opDC7Lc<7XIGCS%pzQOb2_XlruN)*x!~?X z819I8c2X(LjibVm7NS^oE*wG7c_;Y$_B?|}v6KQ7uPaC{4Ui8w4dPv+9FSVD3^?2) z4z%6-Cw85rA6Db2Px%e`!a5r%JmVaxUEEUOPF%OAH4aI@7wDO%Op=xAy#xdMYykI? z?4Gj4`;<%13FMS7QrD`0^?=t#RvpREPlFzv(?w~Jowb&&?go-*B87`>OohfdfWjJU zmR5mf1BCOD@R}Rf>D`g#u}YCG+IyBuXN$95p1KrOsP>J*hrx`aqXG11%`JK8@0Bm< zpfn7wz}NQudUMO3Kl;DK|85K}U4T=2HHEy9&A!>|Fc#TYk^|=c4XZ!}_O}*&A3N^F zCU3ylo}Ic0w7$9?-PJ0Bokl`)?daP{ z%j(c;lcI()(3%X>4ATc?G&zl~M;HJb^*><(#F7OWj5axWr<7~$`1oqzpS=@8oz>qm zFY{jJ^mGw32ri>L!}NO)EtBslCz(NHD==Mxq)oqU?Q}-19c(pB;C`gCQZpEueLZA2 zM%=Dz@~{SGvz_R&l}DHNop$0nK233msO+)JAx$C@28ET|X`vXkLuh9yBZqgag>O)m z4Q-sTUmDHLE4}-eT-Q?kyaIblmn380bR5?Scr>Y&5f$W?!qiZOpUcCb8P+{V*1y>61BYtqJy3EZ&hZu#hV5^X0=%l4+Zfx31OQExn|YqqRGBS zekj73gr=GvT=CbC9oM~Gg;IMJ$S7zU4`hrVryIUhsQL^Rl_=T~S_`lTi@mCzH)tu zlQJvH&G43?LNzfBJ2qMOt3MipkduvdS}?R^k%OC+138jmC4_dGGIDRVq9fbTlQ+PUN3X7N9GSt2 zD};%6`@M15YmdDvGiW_9AxS(MEfX5Jz{Hl7yU&JzbUzJu2Qz5$HMmF)29 z0U8u~*uVHOKpzSjR1Nnj@5_NQ)(Egos5uIk8!)*0YS|}#?USSW% z49BM(jM zP8a*nl$?#MLh8zG2OWlOAMbS&V3dCpjj}_;FI}6?@u!c*4Z9~l;H7PJzkZE7SrYcJ zo1833Hl&|%LFeK~Q7D^avSI;OlRLY6NOUa2sZY z&^x_k^k{`aGU19<_6wZW@CvbmDW2Ug)UZzH=Qx9$#_3g0lR1YAbzivVm+APi-7FeH zG&Ods#l~Y!%6^g$%riI?hc`+Ekd}MX(#TlV1_1A&Be7K%PXlK|1l@Y`sL=2-BGuS`oMG z=6i9oX0gz--qrOZXyy!lwz}&4O=OGUnmMl^_|jw6Gi!QvwRY9G8~AeA=d<(C_@NUc zuD@!l1Yg8iY%fgTTTEsZzfw|)aQ;*Fs=FK}dKi$lkNujP+qtLs8%ITh5`00Gl9N;s z{F*3v{@JFuiR91YaDUX-E;U7cMNA~ZywY%B2j!o$T&U@CFHi>Gi_2ZN)V>axy*J`R zxC>*JEXlxp7Zo+i*EAX#ODL>JzeZ{9)evZ3pXZ!A`)&VPnTSK0YRRWe_3A*SfA1P3 z2{gZf#yb!pN_3=Qx4~^YSM$dsew$^yO{~pgJtf0AGO}bfy1lVzHMUbbLQPBBUUDcS zu$|s04)^I_Qxy)hX;UvyYlNZw{TUBVxR5N*5tQsu z9tFEylbNn;k)sUZ*P|dkrUDxqU(&VH60R^|zeXEZ`e2lcv{K88 zEQNG{jly$69{Lo?S00He&!96ToI?GE8EYbQ?wLGN#r?Xh`KONS^9oB5w~8L#o6T?c z3dRkV2?iZ2k{2%H$p^GIn_utUwbstmyN+F%I;Ppr$Bx@t#C>SyA5ZQ3x__$`ySiA# z#SRA7Y6bZArS@F_(=!C|Dyg9@e;UEi6z5RAIA%KO}29netFJRZ}0wM zr6L3Q1}1%HfjqI_gfCb!l1$Bc>j(mFT#3pB!y`Vop;6sbgs{P|Z2 zwZ*@vpQn5+`7T<#7ea%Dp92O8r-&~|2&BsSn{$0C zvtrjo5^@+oqq?yhiw%4cxC*VR)!K+?H_7iEe}uMC(eAkdMK9`U+8~^*WqrznwabeA zD8kOfkc%`e5u%j>Li%i=rko4oa_=ZE9RbQkYF+MBRwB|xIr^eBkPM-R1EJ!K4+lao z|7e7^6RiRn+V4TzbV#~>x-sWDa-Ln*TLP4T>~spGFh33+Vg%*C>&J&SzZ((O%ucM^ z>46vJ;Fg`mGXl74!006@*IjLA3bgbK?UqAR^<{v|{oaxAn}CIT@cwdbg@j}Cd4;=` zW1q7RBmH%^LDXb1UKvcuwSfqlDZmKE*7M&QXmJ*a&U^Qcc4^@Xz9nl44H(WcX`aBX zCnLfle=PRC>33*#&X|jray}yHoX&dakYCE$Bsuyif0JxU>B|jIVR{>kN%!q=YDCY| zWA57@QK0W)NPJ;K@92^m(`3`oc%_Gc`I<(zvCeE=$8#O=F~BCT`6fb#Z3XkYm+_tT zHt1sb(-+n&2>-bc&|n`5Xirrfj{N@ik6l+#Z_w=Z1n$|FFu2*Dmvf~JqKrtU?LOnl zK*9T(VFl>3d%Fs@;wqZfxzG(QN~Q2wi_~P$^0f@a#k@k-wf7!5$!#6lA6c2H<~Q9P zXcPC@m>BHKq>jmyhY50mWQb?+>wrDIBj$orfbYJ|u z)=;4mge&$DuCl1B{`Oyam&hA{1<@59?DxLZi@BM{;}vk9qcJA^F(NHD5wlYJxgwIv zV-uYAeSxLt``rDE!VIDT2g?(2_k0!iXVPZZ;{Fg%EEBxqi}{|!nybOu^c1NdQppE+GM!;Qf9#P0uEpHG4mv=FY^o* zplFS|H+A-Ww}8~V((KY7NsbI)j`=JsPRrp_Iq~!ICdj<;XX+1ia}jP%=DZM8_Tm~e zxe2pIAeC{iT-2(jM$^r>f8v1cX!TqXYp5D$kXhYlVj3GS*FGQ`xYd+;7gcTozeT@2 zSCOs~k0=c<&Gl6^4oXD3J)u6ZFt>jRZRbhfD0pyqJCT>APgFAuJ~mIpxoiEho!8?Y zV19>LxhQAwQCXTJ4DtUKivEwfH3$x~!_YASD9ftIIX^8=`q^F_0Sd7VYnT@uk0iQv zuB8TrGDXj=3Kr0>d^kaxjP`a+;GUzNji-mh28%SMf|wVUX{Mr-P*eHvws+zO9qvLw zHYAYa8Fw*Pc%0`w=3s}rpgtE+pmbMncPG?$x(g`de6SOa$~9j)69c_&iHw62^?e^^ zSdik2JW~&tkwHQ?<+2bd6axR7Z{V@tJ;tqSh@bi@TXUYg(k760$m;CJL1L!yLjWWoYaU zW>!FSW4|>=1d(OWquVz$^(PRZTib=XWf|{NMFlU`1%&1o))GCtZy8_^>a-Cb) z@jTo`-Ks@pgav5_Bgz!7jX%9$ASRdnP-Xb5jKu3l!6WawC<2DszTvDNH!HI1;&wa7 z6~OnRrPhE#Bk5M{iC$PY5YKvD4%matrDWnOF^GCUipRV1D{QUsxOYfetkT_#+nq0; zwW`pj7ja$if+@w)_a?GTsA&nP)i7go+R82-&tz{r+4|JP1;LD_vU;DA|NJ&+&1B03 z3An|cV3Jm6nkCAvX7ne}PLSMk@i-NxCGo5}UCCW=sqO4|VI}L|^de!whIum^&8S#SWO>f1|!hz(^iV)%gXO#^UBb;wm>= zsz(;!t^!b-^}7ZS--|2LQ`J9hckaHvBE3(N?lbPPSGHS>p7GSwkxW>gpzWrC0u#N< z8bU`pKZX}6G>X_I5>&2(xOdaixlu(sKl;?=X=GZ|3G6<%Q<{D3y@(~s)r9B|T^Wkz zZRv5toLL(5s%npSHx^Molb}P_Bpi;+K_~0Dto?(!v3MVh_8mLF9qsZnqXh#v!2(`DNI2yXkB zDPr_9zQ#q=l_}FNgMZ2IY2RwJKE6%oXTj(EKydh+@p}kSfTLGonRJW8VUjcq*ExLF zFRq1uzMll^R{&5s`;o}0-1g2CQ;F*wczM=OdxL<~`&j5#aa(S^0PDc2!o?Km^;dM~ zqL%@Hea~{>coR>^DvA@sD8d*v!Ul-<0kVHe1N$=0gmFH-H$AfR?>^d>V z8C@^ZZHMWUU#Rj0KiJiP;jX0t`NnJ0--jZ^rf}()I;4KU(F7{l;=Z{-M_q;l`sOLo z+QQVZFE#L{{_#RpbGY*R7h1>~x-1+kn65xacLk+E2n9^rwOoI<;`k`Jt7~thk|JvL z(4K9~2nE!u)tqo&LJrMk4lQkYozPCYj`)A*dgtg$n`V7@l1yydwyg;#wr$(Vj?Kx$ zwry+TiEV3Q+rK@}^Pcmav(Ebd+MT`b?7AD*)m7D1)pVXtggVd%Jk8&ru&WlwBWYva zPcZL6)MY_+Y|*OGT6C-`6(DthPKs90YQBMm!*1(WJ-|JV>uX8g=>Y=bu$fht6CwZS z*qZZF9=_tlIC7B+q0ie@$X7vz)iVChy|KMHoek748hTukpp}J9l3IqGoyv*{AwAKx z7uRPi@VBv@VZW+@XS-iO*T@3WzI9w^D6h*0{{nhs{_e~22BSYorN|@`%Abr}-Pf6b z;i_#a22vV78X}jzq-o1J8AT`8{{nTbson&ZV|d?_^J_!8iM?QdTz6_n!0@y}KPY2n zkz->5sZ-+?=uL7kg|Cjr2RIZ7Ru8y<%j2h_OQ0aQ$S3Xo;@wh-d6>H|APX<9grU%I zOgh!;xigZm_xticXRDajBS=u$QGA2tgRJO+j0VYXNpD*o3^?~-*WbxewIsF?pDIB7 zfcw?c8r)kEh6paZSCx3n7AztI9$jDWO`u6M5-yXW4r_7`*=oPrSJ;9L@k!BX;MTeN zI?3#4WgJ3t)qQ)K!YPnCho|$YGfDi$tVGE~Iqpg6%yJ=&6~t{g3Dg`inGQ)=4`=eX zcq0qHA1Ru2(Uao6uV;Lg@?O@lG6UqY1Nm@>iX1f>nnUjhB-+6X@Fm zH(7wM;Kz&}!R1-!E0KZxGV4;di}%Z_zeYJz8unc#2l)}SmmL;=+>Hh~nEG&BDk!~S z{x0amj9t6$>eo;U+e(Jo=JC&fiIu|--wW)zW(z89os!!vwJf>cGnQEH~~|* zg-LkS;ws+lCJB@fZRzw)(#1almF780?c=?RDD!+EMZ0ld@Ws&snBMe6tz+7m18P1=^5Fyh3G2cN*(2Eo~|u+|cs9{bNs9%Eebem|W)ZaZd_@E;Oh^b922S4QAA40Q#6L zB&L9@@)e!ATZ0CNwNDN&CQ(_J8OPH_Ilbd{2x+njEuV`myQ%9NDF{x$5M+TOOz5>( zE;3M5fOs@xvh<)vt)$}2lZaSe(xhg<<%bI2eLy7Ui#KKduFc8lRc0qRP@oOXtPe{y zwOA@IU;}wbu@V<3@UPsxsDnIa-m`(3-|Ir^^@#1c~`ZI)urY z7&%KwL&HFy2d;G8gs9@(&~Kky(%xy2uKbUb-NN<&C>{Us>YQ)?j-Zeja6BT0qH9sV zlTt2B#%7pERl8f83L*uH?F@-zz(K{7%x}~KqX~@6B`JOS zT8bsMVM^iK_TL+$iYjzzF-f{>G`|jb|I^sKQPF- zZ1-I7wf%7;2uosk>RX{FGd;0VnJP62{-w&@;hGSQWn;!7N z(<#3MOuRs1VJ5b?A_^WExTKKMPZxBk2C-E_mvvs;jRV3;C1$~I?Wa!M%45H7Y-cz^ z4xBFT5c{sDJP~P>DH>@QIPWf%8J$F0et?*Md(Km6PPYID2D$_o{G8q$-`I-$T2uy{+yG>t_;p)+~E4nZb;;N3AMtG?{~89w7zpjK(uDoVIV z!8<5bRBrju#^iik3eC@s@O9?rSm2rGKSR?N70d^t;?rt4)h-o zMP9E|fEukIpaPL990>$$_|BlxL@Uq9zz7vpS$Z@{`aXk9F8ASlmS8y(OE-Z3Dlq!1 z$yaP4@t?^5e84f-QJ}R6Dxk$Z4OwA4Bp)xQlZOT% z9UO>8EGB`h6pY5)s!@&&1S)9Es7h7`)i61m>^E*xQKoF=x}$CjKEZ3y7|8!i36(G6 zcPQxGE#nv-q%WQ4VCe1;Jpu#8ZQiTaz*S~tA_@C~CVoI~`os^wxeL^=+|+8514DvmjpXha7b zJ}SQaeAP@$NioLawsoZI7ioJ(QZJ&K2OwIQQzE^*A69<}O`?P};r$l@b^aFtMW#Ec z{cPjK+3;Njh9)!hIY~)4wiIsZ!xBzQxcUFf1_47jK-M$zb`&Yon4D!m%*!WJ!qM7( z+HJZEh}4e_8dj-*%XzNAZ+Qj`fdC~4V8zI=^!}I7zlYbt_?=Nv(LzMlv7+OQnz5WwFUR&>r+D|U z0zvB7KeCCBroEIftBWC&36mo6Us45%2a-7nPQza^7gx{f;aZWW1|`K}4b|4e!vUs&!E$}WWO9IGwXnHgZ6tfU(I#cg% zrV#$!qe!PU!2a1d?5NObC-tKhXEc<7lNP4lA)s(=Z}jorhpA!{HTRe5fINM}$x7t@bVIR9h^X%&os*13(oAe64@-IAOopZxnU&6t*M^HYKt9t{427Sr1I%& zr5ffP{Mdg#D9^v6x(Pbb?O>Q)>2FPt31dx6rBc4-vAcMz6^a33EbE^8{aS*OMSg zzl&dmOvgd8G)?G4)If3#EF?OHK{AVK6g+Bm=V%`j)F?9Y^>Mw*dj298nm~ADB3Icw`{iFg1skoPUIx0et~h}1tl0Jv zqS?;cWC}}e=VTzGRur;k)4M!9gdx&x@Ym)fYrgnCy3{Iuxf5_y-Y zMFW17^F^B3vP_BtxPZxUiwcWsuP-7>`J_b2c?^=NOcSZ?REJ{;^#FN8tiX}~X9DUV zfb||z8ym~i=qN?*gw)u?afM(ScX0_TpLXBxPk%?oJQh|>^<@99?SiH4i)>Rv+nZD8 zHb^@-rPM%H3r05_q_h`{W=hTnPI!U>scE1}r8qJ@1jDeHpziRbtfOh1!25kHrWIf4 zztbIzV+|}a8IC+8+hnxhUXW5*+FB%zZZYWRQ%myx!otz?XZK)H8u-)WO+Z%dj{7X! z!TT#l4EWUmlyR9Aa0NrNvy@k;6-3WdWT{WvRCfwRhMnw4-(wQm_SdC-S%b-io^j z+cX-5v20$PA3XgZiVa&hJ$Kt5+vs%kL) z8gLF@)4P273b1|+7Ktei*0qeF?9&vjCPv`F6#^=Vm)9y3vp!4Sj1@dZn9I!?V~dk% zXzU|l!xr_#?Fy%|FiYER2YuIGoel}wh{rEHrbn7#(g&+_85R20onU>=jrDf&qQqc9V6A-b`cv%Kmr~WrwAE(XQXk!2@qSGrUwU z`0BTquUp<`eL7+;)3Y$^UjBa38ucs*+j^=0iieN|8fr&LfXrt5p&y}@-K+3KPA*01F(G>h%VItcPbsuj zV7hzJpTML*O~WKJwau)g3&JUr5t;bpXh|yx6;f@SVokhMlZ&yVje9NGAx>r-HaR(- ziCgih^L--F{)T7RusF>9#r=>uclfFGhM5aKY46QD(}c&?&rHOAHC~+cU|ifNvfwQe3Oc(41DK}~hPQ+MyupDW$B z?;P6DFAXe-Bj!6rrTy*)W}RXTwF?>i=ZoUJI=w485Y)#CeMie+hnHrI@6%^pHuVKI zT|!q{Tpc95HBGooI&m;XTCrAxqw*+|c7D?Dse&+28#kHJm)T&q@bp_!1kE|Jy6ij) z7*E7&P|H5#pV$dr1np!~Vqw4!0#jcNTGuSSK|(2GLHf}RJ#;Es8N>|Wkd-Pr3{vC# zEBwVkO>UubYOjcFzDvqD?%4V0;>fqd_qs2-{76>KCCl-2>ov6ntv+~f?ex&R%a{aS zwCA^ha}S6zFWCY-ye&=UZXCPfJW8Vd;Jh^J^(wZo!<-Axv!KF>)XVfyXf%)PcF%TD zn~nxqbG>kyICdQT105R`x)1`lP96}ab|917VV>;(xXfT2`hYfGGO&@4v4vL@p612Q zfK&qKP>6M`<<9AevhS{Y7=&UugMA&E^jv<7Tj4|=S9(w-SXBo{CTXhY0n}9h)*d@3 zACj!28A%Fp+<8w_s6WRJKfCsH#$%RIEmFk1J9g0RB z-?{D1x4oC6vkA$xd98T2_jTUf$W&ezxrR2SpwA4za zUeqQI@l|-JZLwRbQIR-!kKKIM-F$khK>yrdI@P(7CTRCllEWj8BQ_vAgzXlLhzFZ~Q4=d0yusq`_ z=-csBe`Xx4N4>FKd$y@kWDr)N`5r7>fKtQCWZNcnH1O6eGORIOY+>aK23z*F2>zUCfcDU(r>%hk&h6T&(P^E_%BaKF~LZ4K%VZAde*Osv>>tEAsWBX zf2&aQf3lK;-cFq4|Nw!3L#TXR|FV{fcm zQ{!NYte&O;NK>>r*xE0S+V|F4U9F)jtz|RCE`2#o#bvvvz;jlE4hWmqNZW!KA`KE- zW(4a)Qy+rEdQ0c8s98_>8j|#T3;!Gh*kc9m1<;3V&njgsi83{%pPXqrKZ;(}Q9}8; zzg54MwMdwL$l;i~i!mvs9dfK25;hA?*!*^T6x{G$7U)=iy*{>j92Q&xyu#wDMW0$? zdV4$_tm0oDX5tfk2p~*tpmx~nSKqL5-o9>~zN|Ej+YvSq$HDf$4}>%u)+k9Rs7YgO*I!;EH((t7=!n(r_qSnvTMc_^%YI78(^F&*K73^ z)D`5b_mYQH=8#pXQ3aZDw?Y9h&WV&Aw5K`{N>m~R0Xwr!iGGBBSKuRHMA#C(E)r^? z7O-SsBkV-nzTzMw3nXbyyVVe{m$sP2`)c%f>d;<258#O}@X^yK_c-K$=bL zgfAhXFMUxeE99G81`1Gt;O}Q-At7jTH@>Dk*ziQBwC}WD?{6N5GtjpZ?fedLrTlnN z>v&15qkkL$vl<2gWpx{PVVe>ivV%B;k7dw2^qriZnWA*0uxbs%B#o z3b2t_q!AUWN#sjH8q9d@W8YgAWd>i*zJ2;a9kgB( z$eyP=w)&R$Mec`h7d*n3?^m%S9a(>J1&~xTVEn_N0kZ}ihWf8v zQ7Wl?jhAGVyvih{E0hEpukJ*Ebx}M`AA`c%m!p5Mg#| znku8P%b__M9b9~!3)`0C%?Xe-ZPTsS8W56`@vL7%R1~#RY)3!{PGjEPhjhoX6oFQV z)=2KVOiK}IN^Ob%Xu^=vWEd5>k=HNRXjGy`|HkrDXU&C7TL|kX92syPfROPKVo{6m zb$W^cFF^D4!6jbQk}gk9d8WndS?k{B%;Erf1MR{6p+Bb65}0YY4MgNG#;a3o-#V!~ z;|DvwkHXH=wNU~sZL`E}a<9iE#!cC(Gmy)^j;1#EG~_~}%Xa(?-|UBrSdQmf9|zS! z_ohFy20!pRd2OXUFgNc9JAQ|M6CcnGR6`lDA_(;na%z0YLL)DR|MWhDju$C}2d*2$ zkwEK+Xec62;AtSs&okKgIOOa7D_mG{>Nlm!tFd6_(d6(gUHz*l7lZA`>m7AkS#V%F z*rNZ|6E^spSAf2WD4*5k(60PwN4F~4BExU#eA9@{_iZ4lfV|CjWi(ucCfQ_X^%T2J z9bLq_Np%o5yZc?fTSO+A1`#%24Ydw4M{2a(A~6nV2YDC+gY=^^q3Aky{sW8){DiE5 zL}qdp&n-*}Qb#Nq-(Z==Xw4wi%w8ZY0{>zNj^cG<-vbC@PXXf%Tng(6Z0dxOPmjKR z)@UHs(quS-(N$&h-`CEa@fBkq>CE?JBLkRPKToq6sDjx4H23JPvx_e0wNzGq1U7*4G&R^n&MS0Ygu6u zy{S=@qUSDnL>DdpO9CNr0(*iJbK94H9{lHdelsUNi=xysZAIfpZEac8h$s)Z&8I$- z#7f}fqzMHdIOuEt^IazQHU7TKi`wF{f@~WZxtbg9lhVrhaIE~)V>e;v(|`1d7T?Z2 zcT*>Gq)F{nd_3D0&htc09?Tw$07Ev5yHI6`&!tk&>*YorJaa^ysdFY=*MYFGDMp6o zjU7Lar|vBq7eqvN-U%VUWkGCw0^g&MwV=rwhwBpbRaX!bJsC4uV3G-=E~4nx&vRSe z^Z&#?eQGeHGBz8#PgieJ*2`6~>KdTA&e+HC)}4&n7>#|#?;+uG4MQY0Jj*rqf6Ugb zpLN)8Im;bpdGsLhxGd`w=_Hjuz4ZQ8u++3!(dARcq%%aLeb} zCEw?ekLftH!WW8u`b1EgWtBH z&MXpQGxpaiyy}yyohxWjmJ|QA577oN5XcZ{ZUmpvp(Tfk@VkGdx81RDn6-o?rK~q} z5I8jiqs+XY`&uu^>wF#c>>gPJg~dOG?4gqOrA<#}FrW|Bvt0yi+$yC(R>H{XcX|GZJE`dj zRJOjIMEvOK>GO3SQCmsNkxZjYtxXM@bEyLeBjZM*4s(06P#-0Z- z5%u9}J%34`o#lJEa;wi9H8w9=Js%Jg^l7-j<# z3IDnx_z&@=E*xx>TGTTYX@c9>aXIvorDmYvDv2IXQujnxI3>5~yLMz&nIrF|klYi0 zi9cr!EDRh{FJ&U%q{S&@a5<60$faXkipupBVXQwd?rYRFXxOhE zHwS^UK~|kL?TbA6$7wbDWh{2*1T)MUcwL1qJiGT3ol38Mwh8v+$J>Eh&4xgEsmgkn zx=r#N*R%>Li-Iw2dYeo7`;ZF3UR8$$-fFAjw%To)R+wr5)9R0ix^Z7d8#mr+Kj=_O zb%dx>T8`%ktffq#vHiM!Eg)DlFk8>DguTQTiAo|JoPPx#38U~8jXyYl$E^;DJr{}! zG*TWZl04e_gBi10a479h_nA-q<9l-o3yb*sLi9Hm)*)@|jL`Fyy4+VzQMpMbMG7asWHdl=pOV3*xBrc`py>|Gi&av7)y zm{kd5BZ%rnlGn4(`uM}xy-c;c(}r?_L8YtdHyP^oMw^+iA5AQM1*1#kC@*c4!FH8G z7UO||h0+O)KVBTI%2c9o2sCWA6&X1Uz>#UYam_?_b|El1Q{fK|Y)VxPX^yp3S6?)( zCqs<_mE{^Rr`s=WN?U696wjH>L;@NJoEFd9^yp@y0}Pj;-MlK@`i#JS)3j>iNqEj= z3_>JVSwx`FtVNz8Msz=d$EMPQ^Ld4T9}CI-P4k5yV7MguI~|ArAOwd2i~2*M8%1sPNdG!%$+>)4ut z;S=_vBIjY z?tEm}>zx^lfotRqFD<0u6V%cD(|)@jQaf@$;WzzgkfjfGu7(Jx$P8v1V4Y zI#fm_I26`)UPSq8zyhpatZFeT!L7TDZTVElH7gD|+un@m$un;b%G*!t-1ecZ*Y-~@ zhRGK>aBCQPS4WHuY{@H|UPXU26s=nhv(J^rGd)ui+6MNIS0Px)4^%a-pt$C_HcaZ+ z>$Zf>W~#55iOl0x6<15Iu(`HydICUE`v;in%qhCf!9&lGu=eT;Ws-&tUeyg!&zh>7 zTb72b?2PV1mMVTZZ1UZUj+gLCGWXbRsE((KLeiv&nU)u#0b41(rViNUyt(cDV5wdi zVtdRKrZcnU0UI^#{qZ6j&!uFj^T`~3=bbCJ;Kh)@I=3f;GHpe{V8y^DOlrQak7?f&5?Gsi$+McH6<6Fa57mOY)8 z`EKF!(w%#|)WBIIbU0$1>n!Gy&2V`%FPzq~FRmIXsFbujqn4ZM%PfXnLfefquAAGI z@V_kd3v)DJnWbrB7pRR-o1;p9yx|V?;>LI<#F5Z!G#`r5^QOj3oH$+Oi|m!@0^_}? zE$BDBpe_FUj;O;S3rEbJjyYC`PZl#!iux}-BO+Ts_kj0n%>6wo^&y0Q|I!t^w{B|< zDhTVr>8#ac)v>i$mD1Pra7`77$Xmmzh z6XHB!fE@wR_r~}`0A$dy-}ZdFWTsYQ`dpVkoDox0t)ipPoqMsT3-;u*nUc=^%M8lH zWQv5Hz={LQ*qPXYHeOk*X|YimjjkWi2hT8a|)wO9d;nwY}9_*%ng zqH8=#@EfK+SzHbC1xpF#+agezt|U4wz&(987S{D!=X~xJQh~pq>->Ru8LB3U+Zc*@ zp6j^i(9@?h!R219C?umBbyV44*fqI-?RWw$QX(%C=D+8Roy|lXdTg=|YKGF*T8<4|NC%-e5xE2V5wF9!?eUk}a>g306)9hCr=G2vVTAcoM zZ<-`pjZzf&A$JokdOGiGz?UTXltWC{nHE;9$ z{Y_+kuhwM=!rt4E4#c=u;}Xld?B|dc6Gn7ZKpk)aMf=dT5o*LC(V`@8 zUqco-S4Or|m#6gLJ?YMIo5>xfOG%G8d)UPgCY zAbluM^N(6R4)fmU(2buf zAdS}k4}U~(Pcf@Ie0OwiR3yc~#~cx@t6aVexwCNsOFG+60f{{I_W&7x=)?+?>YTOLa>!N`oPXsw9k} zDthNjDbl&FX4gLmvb5OhiC2L%F>giPuHM)8+dJJuXtKZB=`>)?%6>(%{Aa=tlEf7( z_oonG4@C1~hDe}}Li`Z61Fs&yIT%ylk`!wK(0_#4Q)u=D#!GSGyCJrD<>q^-I zrASNJ;*YOiRdGflfKvsib&%;h)^aG2(uTibyw~;Ez3W{8kH{1l%Mbv(JHj60B@F+% zhiS9R0xig10Cxy-L3q?s!4X=7{Aa#k=}mmri!W4rS<5cKq01*Tvd`EGT;;$Zd8G<< z7^GXpHyeW<0P?x-I-KqTw}bwzPmlL!;J|^^g>O8R?r`c))6kNDND`N-WV^Abh#WWL zU>Q}D+YkOrlK8@YEW$nRdTwgpe(Y>mFvx0p_(|wJn3PlO1)vPv=R}*HzGr)`@a713 z?gFnb-+9D&yC~ZVSrAtCIRqu;F_F!=w zittanX%eKZr1YMkM`Zz2jRIa@8YsJcxjkBI;+POLQaG68&@myP_Y`#CBcp(BwDp3l z$7O_eMR!e!p{LyjrW(hahc$6uV`NHd<%5aNA@U?8vozjQsj-yJ=d-cVXvQUpy)VkW z^Rua=;m8U!iCRHQK(jybMWdKKIdb;CguXfG){s%)wzlhlVAToH_C7R5k=31LZ?`Eg z>LR7TNoOciN)ksN zoqHGWIvnTH_8(v-8mHju%*MaUW(h1%*_5j~b>;yoo5xu|WfL#>FHtr%hR7~rAFC#^ zDE*PR(fDG)V=+MP0%LLqKpbNQ`@v2zywdLLNQjEowLYM(-xCD5oa^XI$ZlVP2735~&aC&T z!RiP|lC5dc|2zuS0YkBk@|M4++y$7z*4Y?B(DqY*X9)w)C)0~g{ukW#z5fF@ zJ7q2OSV%f;EM3ZTlR8fP*u>!n`f57XWP5W;^c61$fb{CrkvbSb#m<xH7bK~7LpuxLy?#ID6e(->kwa28%6rk=+x8@Xj ztfw1t%-W!>gwEa9@er>Ic8nbs3!V<1Wln=2Xr#a()u24B!*!%|tg0@Jz`TqR$8){% zsrrhG4C? zOAV?i4aLweecgReqK2T^^n!-?7h30H<|xI736CYXLc4&@bv!u8{!gZUR7du~t67xc zCDT^6(33Nsut&3}HcGgj%{JP8`R@f$gUD?3C*AWg_>bC4V_m|9*&{-a`oLSOto_cr zSD(B5ZjJ2S&7WS!K5l#T8xbMa&NotO)*z?!JD2^gv%O7UZkK>=63nWxk7vII^)5d> zsON*d-pNaO$Q%8a%Zs(1E$_81f)7r>i~#@g>1Tx?9D>_}e0I;e=O(h@)djNo*hTZGV-xhR=cbNwH&{TRuVeL53MG& zdda>Yyt6hL@4sX6v5X+;!(j;U$|Wd;JmEyR_cJ2mO(Jd=xKJjHFgzVuvmYA|7pq9L zKV)7mFtq+tJ;252xZM6Yi@LkH&W(!?OsfCHa*W}1i9rG0kOT`XXNCKH9c8HMGuHwZ ztB7EA+r0o=C(|vxYSW|CgNLP)EdUJc7j2NP)bAoyDbFjYS_2tqRgzeD*u(3@fXDso z$&RrZJNvTyCn0lp_SjD*qow(E{7xRk#F(Aqo55GhAW8NMO=xb#7~?+-Zt@m;1FrMs zMTuneYgSDXM_!f-weY0y?tbj;uKA+0HXNB>8U+-cBpEJPE$<>?#0ysVh(oO?GLn@B zaT>8XwCHRIP=z%1M1NACJ6%uSCP!}`P3B&SDsEV@dn!Aw*f3+Wpv2dX*fx}*5SuL) zg{ot2d=TLc9IRsv__Yq@;rMW|foDiz48la5 zx4w|y*(=67FIp-h$l`hF6j8_Jg1!AaWtJV+*3jf*?4_Xh=5r^PlM@3~$+?3^>y1S$!gRs) zzRaUn`}#p;w?u(c&L|J1D+Q=^w4wM839yuQ8+7|N|4pXkPODC|?anc)>!j92| z*cUL8`0yxS>jGMno`fk zpD!m0L(_iaw&~h>7nCE4Knx(`a$)K@dpA+c+2z_xJH6p|mfw~{eFa$>$^hL0V5p$@ zzF<9H^ zL1<19YTw(kt$kG`^{S>gnDRg`kiNJV;ynh+ayvlUrv2x>>gKkY?nt{lz1#KMnqVwT zx2r=_+g7(Vr^rfo#$bz4jx>RCZ)<)Woa)h9vPhYog>4u_z!VfGT{1DNF2-s)}*OCnL<65(yr_Uju!*uwO@2y|G`t%#0?$&!pGd zdlNCwdE5uO1yng#*-w83lsy!MZ^7HLT6!0tqXsZZjZ!HoT4)>%XZ}pJu8;>g9SWeJ zo0l3E{b|B-^i3S~c#`mJY+$`4FMj+{RU$ZFM$=w-9d)BRA{-@im<+~4V08grvPC+H zP^lw`g5|DYf3EN6*SKV}NFQ{>S%04{6Mt(yjN)J2quB1suQiVWgxPra=V>M-XAH@; zhd$Np^&^PhAk?_SSvXxEo;8Aap9i&B{JwqDjh<}uocX8V;`K!rHXN}HW$rp_I9jAe z5avM*y(^p14F@#cjSUgz#fj-#D^x89(h0MfRhQunKGjE8xuk8L?x~K=2^9~%zd18p z>$#N&qMzzuJZyYqzw1D%Na2KJ*C$Dku|+jap~VF#u_b4-Ga|2Qkw*vmNBX~{W56NK zc2B2^#qA)+U?Up!e~l5{P9S;+tiCa-@)6+Hc-ma49DB~YgiL%@VwDWi`2G7SFK)zh z@B__!?U7N=7oVS*2WqsL&vb4G;6kR_4Ys&1&k^Or?+bOu>z(nA;D<-}?x9l%fz?_n zb^vqiodp+FUNqQ&p6)(>`xTjh3_AX+WVz`WA6Z#`d}75DOFWsLRyb z-|Y0_+p`I&I&@P`s;`o^AL3ue3pThKmzr?p;|i}Zz0f|Gg-{m#2~i8fP}{M3_xh*U z6Yyj=@QuDQYXxGl+ZidEiv93GZ9 z5J@O}GZYCOh<0Kecv25$;&>TcZXbvl&k%`L5>fOCrMU1Z!vG}m&#JK%aXw-<)oL#R z?2dGWaI2wen=B67FK9;U4yA{i`u4IjnGw415rZC)SkWDUuc7wt#w*&Oe{{+j$t>* z75en4mI}uv5Fy3gfUdc$x&?==&P93zWbckTzA*Q2;EFkdnUrKe18QHsE3R%IYejuEM$?^Qzk4Gz*KaCjp`7&^j08e$pVF15ts{YPP@e%J! zR9FVe_&eQIoa>x@f7H`G%Jr)qG^7y$BnP{oKIPx@TA9 zD0szI3wN8W+(w2~neF#oBgKx9Wa&Q1wIh(DTatbJqcgqNn(-3-)lWoxjBmf_gnXFN zXTLXd?w}w49$O@C0UH|NA~g*=b%{FYo14UM7vFM;fZ?9hQPG)mPztLeoauD zv%%F>_N8G+m(hUc`0#{wOB!NTQoC0{AH4l^MV*sKP0HsaM98L4soc8JfR1CGR^oyY z4E320ZMLM5_KhkR;xR_&-s{@w?&a}P-vo4c?O2Lwj)VAMY79dFGY8TDSx zo&5r6mrs|{IVj-?Qq<82{F6>sQJ$8UpK=nEg{ZU7Cq>RI0^Fm&U{GEAMOndL79>bm z)1@bgBYqCxkTI)15CIziG>vjHv-0gsvgAx}mzmPS>r^(Im)gna{P;X4QKS~yRm^7F zkHQ!9>Z9NyOodRN*?u^1Ifu!};PKxKO_)X5Z8nYA+|szo4#1Ukj@dLI4bNL#ph}*#D9U(B zxka$!W4MB0%qqBUxuo&&NiVhB6yaMwr&`ZTHv$&iAFy5ThO9I;WV+UusE z7dC!Pd6FUqQtkR4NiyCQg>yer9S`v!$Sf^7Y@*rApeSbIEea{`U+)ZO z*Ypo&th^^Nojw00Zfx2sxqBs|k%RoiNP}mO7gaGZUJG`Ri>rJp4kP%^w;$ zBwSc@t2DfrrN3U06jac)U!L{g1--n0GEEJp%q#Z?5I3&DJi_?;D)_*GSHmyTOy-$H z?@3Ig2ofua+RMBiTSm97v`EhuY32@1t>f6l-)zhXYvfn%QMVv%oME?Bkt#p5Ha^M; zh2`3_;4}|GHaiN%1};`n2EsXcu?XR9ln3t4#9H}f%ClS3b5>O_^8@9W(2=*})}kuh zMqNd{^_yj6karl65P#us#JOTAyZ6vkLmE6bMQJy0Fd;L6RQ=;5NJFpE(1yUv60r1D zu;$nu)UdjWp!YIor0B_w%PTDoifHRKX42?lFIL?>HP=_|kvp>9aqFQp@h+~~O%$E= z;_38O5-#sCRfZz6ve3VC<>UU~`e;IK^Og}UyTao9-;2||xL(EOV!3E1 zUIqcd$L{Q=g_OrjV`g7)TJea=u*Q(g2hsUR}dfRi6#2~hI80QR#pT7+N zg+@NVne}!hpVjx>)J&*yQB6(b4naKR;T~F?;moX`#Zd)H>rs^@l7Pto!r*v{VypxO zs)F`FoY>G+(RyjyQ&Ne{ab_cn!Z&|Wo@7+|sUR*C?9lRzail(xEhGTD$M$eOE{qwj z4$3H#8U>1>9A4Q8js8tAj?LUUd=P_b@ahyx=nKV#9P)5yW@2>=YpqeWX@uRRJm9@l zpX^7b61$cP*^ey_jtuRs2cE3OenqV#X^*oYB9{D=fbR+Q|4Snp-H4X;>g6YY7gEuO;0&U z+gjC`LEk{dgt{qxdgFg5-L_bv3Sx^94uR~hOrF#*fZ<`gu*lGT5 z)a2`WJ-^N4x>!j_qYt&4r?1g!U;yTl)*&7`rn`>gGEu{>|0q*(qlAp&JK=$W26g)s z-F`7CRh9Q=vY+f@d~FqMdtnPhIf}UWUI@A(P>95x!rwEAgO`?9-&&hm1bxp~^uN}W zUdU|Zde^_F3$b%IaRosUs5pC2=cOyimi`$lH*1HPEGx!E6_l#36vfs{w#w$sPyeo} zDx0h*8^uH~EkSI%g&)82;!6@7hO~Jr?4c#g-p?)K;rqCo^9m?jKhk~qbpd{XRN!or zx+M(`3hqR4ZXE6$vJXo?#mNgTa$D+ZT`v4T*4{EGkFRU< z#oawvLXcp=-QC^Y{SNN#?(PuW-CYC0g9nE|a1HLK$@70_o~e1>sX2Adw^h|u$)@kU z*1E3W+TGn&G@mFIhEe3afo}P%IbECoyC7H7a;>$#)2VA|we~e9ZXuML^&$@3KxaKF z5(ew+nu=I?7R&a}rx&!lH7I#=iE<|A7EY1zzS@az!; zmdcF*OY4l1QX;Liai?nl+HZK6X#b(PuZ(5&us}eoZ|nfnLgOG(&iA~=N%_Tv;Kt&} z;#j+d`SCRk?;om>eN{ckkX0uC5wjzm8**s@Y$d<%lnFzOi_Wr<6WkdY9D9j zFPoo9yMqfVv%*4-%X1ktY^w8qXfUqcy_?nLL9Gm}i1$PryJ0osC+DLu#+vPUqZ0mo z8ffD{Lo*O!AW5UANjsqC_>@+_8S_$WfMC@Xm`t8V5CgYssS2HFx_d*BIe{F>09)U+ zVU)=%OYUb!)bBinxae+mD6{Gqa@0Tj!vDO#pZnp@XUJ57x-p`auCGMD94AqcwZ+gB zygnRf#}6&QBgDXnSM*H-3kL%2`T}fV;o{m_C@=2fA(pY0!cv6qZf3FNkJM*pQL34l zvDZAc+(4<0GWTjZr4u%T4NwrD#&_@id#N0hm1xfyyEMf(ez57!?*10zKx-q;{S@rD z`YqXUQXN~5&>`^~`j#Qf2jES9RC(13>ZM0x<)F6&s9=_qZA=MD;SlUooI-czt+5EJ z6a91O;RbM#Und=0mh9$=>iAN^O(@pV1;EoMSVK8ch2fI4q%*&nB6#`|<4D3eaCHt9 zFw8PN_;(pk{&coygq>@^*)HUhohYT(9AtraBmNE%Vr^ScE-0JCe2v&Amgmae z=x58zva?F4vEM0?7CY%#*N=WhiJ9Wc)^Ax`|E=l7DZHL)o-K6ptor%YpWdo1TJN=U z@jcws5yM(OX2=PbNe8C0t;dGAiEA@K$yYV~)xddfNnSZ97Cd~NK$wzIVj%FE`XD{G z`2{(v$zVXI&cGOn#LGi1%l-4+a5^c@I5mE6F!VrxVPEa0sb+2F0Ds<4Br3Q!(SppkQHnLpjTHI714`L)PB1cBy<+LBvbQg7OYnEj5 z4!NGM0KRFzx@xOGmEtXdMhu--W8804vl1QflX>|v4PIgsWTee&?9`itupKt5A)#+y zR(4c-hUhglDdE!#pp20!=v81~d|i03zANgd~4n4e}KER}nev{4KORtl+s;^tMB z-h#6-!|?iyDu3o`YQl6*CnjYK=FuBKAeA$7<{o>!@&z zNN9A-I-`#Z>Ky2xrGvEf-s4L7^hE~>xh7<;PdRx}17({ktQ@N4$9+IRzon8+&9SE< zjR-e+pfd7$F_U$eNh;TrGp4VCT+&}y7P!V>Y2_T?pT^>xo><(TE`rNkhSIIR>*B^$ zxi>>r0u0AZ3DBS?g3K1arUS(k2-Zn~ci%DVLhrFSUL8N8f{IHx(Tk9KpH|Q(o-~&D zN-`e_EQRntj!*1P-K>2H4?t@d-amEE9^YHy36~Z%;B7A>W zLsOlS8Pb4X?P8&OQrkN(cY3_xs?FSnFBI@w>w=b~55B|QN5ZarGYJdJOUFJ19Y;k= z?x-cacC%q1z?KJsV>nxf^UTZCa40AnsihJ0wdThn{uQh0&0F9(s8QWrT(OzeO=rzE z*>y-T8!^Eek6?)6dPA^8H?P_5=CMO+G?RqexXP*94 z2OmYE`2~;Mh`r9=Ecxh7SiDEJbVKj^#9EijdwB$llgps;%92I$dqPx0I$Pe_4Zmqb zzJ}M!;-qXU;6}((Kkr6QW|ivloo(@h+&sEf{k4~r%Wv-8cPn8eYKTijdQ#u%OmOQT z1L`v?0vzi*8u^+W$Ex$@j2l^&PMUP$*CL7s{f~uQNIK%yaO2lT@^TBlKr$GxBXKy= zcVTe;PLifZ4-d`)a(V~L0fEz7k@GF5Ij>X5OB|8!8k-HVl4m>6r`R_eT28~#e9A4b znoaLZ1Z2*%aJ1M=9#M?6`b9(1y1%9=mFJKacQxGIm9jAM^1UavBjF@E=FH71=9|(K z432Ey%HP%}nEpv&CaVz+|yY{@ee6#Z0qo6XxmkVF|9rjS#pZq$4 z7>TpJ2#VR@D2-P{1MDHfV^Va0f_kk-MHNrls(n&ne?8pI1tQ4e%lO0m0!OHi&x7QKh-Vn2>!T zClJ~iP`iNOtk0+|KeIZ(F`qkQ;w%={gdAHWiG#;w411|+|6JqmuYpms^+cw8URPaF;poe2;6l91nA&^?$Ura%n%UMdK4Qf!CHs%VGJ?;v@Jgm z8#!J`I4|$V5B8coIKB3&lJtNVt#L&(d5}|eFy~w8N8g2W^wM+$kO!wD%NWxzXJD$9 zwzGmMG(9R|-H?lDU@CXLck1u_L44Q!UEMVyc<9H-C=Y1BC{IlpwOBtV zFIcu?@=i%_8%W*IL7$EYp(n{lQw*aN!2*{VhGtcs(~sn~>W>y#RBzorSL!F)im@9` zbsl!Y&TGq}btK5>#*Pg;n2c>~4!x@Vz8Y{sel9 z1fG;c)g*Z~7P2he+SFV0NdqzaQp^mZMmlW@@*Pbq)J&}AVl4c021eB#mhIERx zTm=H+RyMAjh7|ZrrvZvLVzD(@_?4czYEkfNtopm!_bEvtvc{`I44_$okpBRn=dEk; z8qsR%DFYr`Z@zxV`Lws>3Tdyfv$y0dtk(t>O#_Y=3I_twzp7}UFf5+StSQ~|*F1bl zLe4LJ8lw`_&q3bN}iwFCA|r5(O_e$nbexR#teCX0FF)g1OxrA6bMkyIZKbtZr8 zceTq+n0ol7$}X-Y6D^rcNmI3@D|+9k*K2Fz#0FoK8?BCOG2sdQ318D&WQNT#&eZG`Zz~-OBA^R;(x& zoxhxf8}s}-jO468&hR^f2_6&Dy(rSV3%$@LPRH3Uu7dd}=4zxU95L;{>EcmhAn=TZ zl4f0&!OmjTl1xd_w48LSF`rQ`<)dHsJPDgP^)4%($oX+I^;F29J#llrJEibtGD)>; z?45D^556sGmAqj|v@i*Gf9n-%<3ZsQOHm%7=ugIiPGo^UNx9Jc;X4F*rmuK_m*jU# zVX~$S;WH}U#04;!8TwaLe`SjKm=BTBqABF2u4&)&sld&kvB}a*=8G#kZB)m_T%{EM zakRYyctKUfH2z4jJTmiB-KR>IUuO`F?zty2Pd+t_FXEmc$Jg!Df$*l2DtJ7OQ+*k{ zlgHh=v#!92aO_lb%$&3xL#au72XEh}*dKadk6=%G@a$hN+<$UM#>SfM?>*TeTb+GY zjUu~$iCj3otyuG|%6MjL>Ps!{vRyc)T0Bl)5c_2DI$ty4z%^;bjI@L-O#He`2c4pf zEbMXi4pRl2g|=)tax%R{%5jCz>Z&s&9Qa{m2q{o7eKj%Vfgc9b^;7c~D00)G6kFZ8 zi-@5V!|hu8pB!JFCYIf(TbQxdK8sGRF4auuL|eCEi4zjs@Bnbj{3rAAz32(2GW6cD zyJdL&Zlp1y5t*rJv^L+Tn6yhD(TV#wYc|Aj_a4&;_s~f;ZnW`tyT=)vG~`k&d+)oe zZc|Fzws{}-F!`^%)eoURyV=b|hTBZ!43~at=~zJQa~8G(_7p*+VB$!zJ4|HpU;_T` zQm`nnbXt5I}JqUI~&We1+-}TS?SX2z=d&Mskv0-duj2?+pqZ- z#?qgSBhqX2Km)D>i`vYl*8$i49=Z*Br2D196Ae`@zO06A%1P|HB@-6KH@%jhofjEM z5^cWk+%5WO*DZ5?vSN~QkFtQH32K5d1bPe*ToW^;&{mcK&bD$A2^S})krjrXUZsL4 zexXKHb{*O>xKT)pr0TW@vOm9O+JxD)w<1yIIz}N@s_a90+&DfNGeD?;z>`uV-((kP z9VT2%H*;WW#APFTtw|29m>_`jrD;R0)Tx>?hE|Btis0k;GGZjq z1~h1Cac~~%aeyA%fHRiDHay*#r$m*MCzFgz{k$JG;xiR0Wmx}rW^sd@N&Bk=<;Z%s z*)4civJ6_Y&mf0k3V$3}{96DlzTEw-@43@eC7xzoD2wu17klnyUCcIcTxrV3>@Rn^)N&u zC`&rT|BM%jd`por4ttd3AMQnraG<=5?Su8h?*Rvk2PYy9U(SipLzk&;ugh6$@px!R zJ)T4?lw_X5j4#$`g^OCqi1i!SRK7HvxoXgCL(etVR%6fQokVjo{%e7;K56-Z)6;3q zcEjss5chJy{IQ_bw@0puh*}}`O1mde2iBEWDZ(uGq)GOY7K?MmpAoe$I9qeeZfP*E z{Qw9?lY{#dbQv7XtO~ssG)pVZv4`wT{gI0E6zD=4%u|N;LWMhs1PsB@Y+#tY5$b>LR!s)5Q0mDKQ4je7U*o%~+HY2ikV9MAN zCGX+>80M?qNPi4&yJ0#)d4SkqzxJ|J{~Gu|W)w5q1$E$4ScB7tp{KP686Z02O4Iha zcJr7x5q2{BCs0qYT~XSXbu$LVjyQ~~*x+zLZ^&R#AYgiHV!p{cOkFLXytaX48Ja3- z!gTlju;_C6&wiMVj;$w-c&%7 zIHT3ks~w@ThfwB$uI^rx@r%1h2^c6$)@MCY*C!<`vy_A%+TYpshbHi$88rH zP5oC;N+%zMQHb)oX|r{hu@%d0IN1#WRq>a+eT;IJ$aZi7;r+y`XRJ2tw`Z&Yd`mb4sAB~j^kkkvF!P;3UMEl2D3bqoQ#QJ zi$N8fwh_D(2b6L#^746cs)`a)`>kr@96JLwyd!+3o2gzr(b zJl`)KlUOBsaven~2(!=q-3?tb^AQJK<_SOgwXQiSM#~Jw71AhW8euSjN&o~`Kqaus z2Yy5I{7I8ERpQ&U96sgIRr_&F@h>cu7S-?nbw@o1*fK^j##qSaKTA$v9jouh3gJ`p z-jP1p1714zT>Nf3^1&ED$`1ujWb&26K_8-0u}3=XzY)=7)bE8^vt2eMYWR|APsFvH znH@kP+6J+=??!8dTQ($)=SAoF;?r$L6|7}pi>2ien7s`Ky1iuhlYit4=jHyG!oLDC z_A3y0e%Gk>d9!&wT+B$b+ghGh9#UTc@#h*tX`wTHPL72I$XRbkgb4Ih6i>@yo*p67 zq#U(03#idMJF2`W1u@F)Nb0v$QWt1tFiqmtv5Ys6PbFd1JuSM5;Kowp*YUJj@8OLc z{(3>TWJh)HfY%M8LveM}ZMXQMNh^+9b3AhUfGD*+I5}BPN;{1#4Gh!5^!SSNwa|iT z1sJA*qzLa9G0dk2c;AT<&Ke3& zB%P>J#*QjbyfgdY&kO8tV0XfKdMSF210yBxIh&ohc@aiQDk_>7_rox*mrARa#q@WU z?OfE~y1r**H0}}J2~t}AieQ8fW-6yCy|5^iCF_`D;|~}d$s!FA8%32UH0A`$(t4ZsQVt ziU8f>0=}d-(aeD3s84g+bD7jUakFelJqKdSDA>HKYx)6#IqG>f^1w>NARv&aK187! z0Vn&4EO<(AHTZf`ZrGg&de{AW$>jl#dQFYB zt66$Q*5S+m? zmWf30#XrqeN})I{sHAV!>bw!~vp|q?Y$>X0w>;Z=ld@~c96Prico7ukxq8`3ZC$v& zMX#BLD2CQ6xv~9 z=)jqWol*C_3It}9pCg#f_NsVPaNa;;->134et5`#$S4bFcxdm84{$uHU>w{(@YdQ} zSVR=Rx?Zj*?j}yCax$;%sCr(QH@ob0m?6phJROy+YAK|>O=;-sjS)tN8*kl$SCALc zn2-=W7)iPi-FEJzj(@0~cpdhfBPc;J^NnY}q7FO${g1Rq9fp%&A*cm)cRYoPYAKCG z>PB{`JTem{HwrcC^MMaDGZBqvWmFFy=>0ksM%B$|as=*f{~71V(IUr8 zH9wEN_mw?1pL%-m|Mm&VxmlBqu3ziW%W7b@RMyAiWJ?$%?~T)lyZngIbZhiWE`7?4 zdET`=Mj^3k53^qSfSaUA385Nsv8vBm*%TEBy7nS|x7k#~OqN#X`SO(IF-U1z zmOr}ghaIk~uqYwU2OkPK=e5)4wGS&uA)YTMQnZR;X-11!zLV# z+FdyE*g0fr7tk48ok>4bET~zoO<7r0A@^vn<>l}GY?h8{A?%q3n@$li&UKXIQ@6a* zG0X)*8UEZQLTUo=g0Q!w@O7v872xauoD;GtB>IMPX#V1y>^Gbv|AupN-f)f?0;KbR zz#DC-)BK(wgZI17ajT8h%?7}+KyYhNr!)1YHKvJ*j-O_?G$tzh&j0)ocs`LLF71dw zi3$E6Fikhea4ltX{NzgOY3>hdNg4{a53u<2H?nhO`pg30*QYt!G5I~jL8~oA5V9$> zQ4Nv3veLA`am*L?$aaKJdiV)T#XtQs`(@Ycm9=4Vr&T$w8R(3Q53G(Pdk6{3)pnXs zHA#{VN@|)5oKa5M* zjrROJa270l1mxflH6>y6PA6@7X6WbSv;uRGkzo80+%89F5`%ae>G*}{1S8;}#;4;I z3$>&T7n86$uU=QsL50}Jdc-)|+iNIG%c!QF?-w)x%*WcjQ^`D4KV;*CFPrU5p=--q z7Cm6xg<%`?zI_Y8O$1wGwBYcDJi;V-aX;|MSOCWW1fI1T)j@A9JSb*2;c<3LdVuU& zig4=yQ(dGne@ps}-F!qq7X_QO7o5PlUi=j!2^@2E4dMoR<0|?ooU50n>l zK{#PI_Su{*Jpy49jFztTDhn;K@gZn#)^)OnDDKSuD7gtZj|+;Aqplx481|nVZ?5nD z=u;Uy=}yY*zeqm5=O2|U4skGCc{ChkAsepd6~XQU0-j%pQDq!1tQf}n|C`+uVxtEb zu2THZfkWgd2#_$N6`NxTi*1m(IiJ$aM-ja|V&G_RKh*w%ZnULCCDBQp$E}!e2Wc?n zZserR;IX>lH!AzsftmsYou2~8AIzFzCJ&3XwD?!rjEb@-C!jOe$6O{g4qU}mlvNsI zPbRaWQ=+VF$jaRt+BS+anXl`Dt%O*0m1O(U@l=f?%Zh#Pjh||42{N;u9cvettJv|i zVKX1MRTug5eXe{H^^a>_pKf9wn!8@6pgxj0p9~7p(^Y$VYqMv+zb%ODdBJ=v!JYQS z`J?RV)3jd1 z;!(dx?E%NURM$ewox_Yb79KyIh7nlj%Wd?%KEF=`AUSU%9G8hW^b==|OlI~q8fLAR z$v;(O-%k+pfQ|$AIS5DSF+eD@%D;i{D0(LBX~}|KcId2pMr_uKj4 zr5n{12pR5lS~H5&6Ne!UvPSt<1l4_e45*JvdIp&;D>xSlo?7U!3fm(ZXhd&^7EweD zfJ2M&jXbDyW0h4iB2h(kd@@PAgbI+#0-Rb@(>@uYB*wGW&G_fk zq74sg!3?UWah6W6dBv}WW?h0Jgzvvq@n3#o%5h(~f-iMWxsdtvEaJTubTzfTUW{!G zF5fIbArjuwQ+VBYJ3ig6_w5cwj5WYZ%Q!HQ?{Tzs^S_8&(EqrGyw;B@eZAv3&i*zm z&U|I>(+bmci$g6I;q@vCx_O*G?NOWX$z0x)o?B-J-6Vl-WIzR&jjxA`W}HOkMelr$ zL*B;%hv520%R)%HZlgxZ5b#q}PDJ-c6~BF+2T{ju;C=M*r6T!-W*>GJ8Q})((u_Md zm6hBWTIB+~>Bi3Qf7(ddPOtv~I)rR`R0Q(BokQ|FiuTjFA6b`~)%%(EQ;|Kpd;V9V zvlNXqr3M#WHRr5pf2^%O-j?rOFTby5WA*_w>AF?Li?b2HIglm(a-K21B*E+L^R;c_ zKGgQ8i`?qeZR+KI5Y>Al-a`X>l0l|jyki%y#pmNhLr%z9eZwtj?h_i2S8qKulGg_pv$ly~TC;B8) zMBmj7cx1QH=oj20(7_wx!607(yDNhpU?`8seW*3yQ@aWNf$5&oK3JMxit_5uw&ea)Wpt5v^438ihjT;{aQiT|wDVYQ(U z#ai45u;6B+CP&#B6U&ii<}8C&hM>1eWF?v!+|d}q+aw>pf~En&k*-XfljgGuft`4v z^KZnzr$T}oyA>@(HRcQxCsx606q%yUvn7O?NYYs)%*u}if#6o5R%hg`x{f%JaW^_s z#+a ztoeOxh)~SzmmBmNgw%Hs3TOhB`f11v&4O1jjff3r)~|#xr|dp{K^6#FTg}*O+KA=K zjt`SlagMI=`Lnuzbl!Npps1w5l`XH{Gbw*E!eWiN1WdiHnVY2!LHU!i#?C5E#P}Jn z8SSDoLvmO(q01&MO)pcEEmdpbK_)kuD>p3HCSeW~JakD(Pu(xnF5eR^6m0aKMx%1s zh+ku7dS(+hZ_bT-SlXfymb~^;maT!;Ft6hj{fy>q;a}Mox!gBHbt8Nq8i;ncjl|BO zX>+7<5hF#BMlQ>jymdM(HXddFTGg5Gyg7AgumOwOO-zh+mHjDLoRiqlAGR-Wv#)G! zw!OxXD;sh}YM!3%7#=wGv|>?JT{R-+ShQ|Yhh0app!TJxQa#GhN>)KWP3`%0sen9o z5qK^p^2=$stWC4{y=B9lM(sD!yVmwP&5djcFM?G)u|NY^@23ZL_s4#DAM#h;hy0`K z=%GiCF2z@G-m#tyRK4r*3N+1Eu=teo6)=N6*q>h8=@1&HlEYwXj03zUvhtD)?7dd1 zynfHPV2FOuy$}ZAwO~JydVN-(J#TYQt_Padnwn}VNWlm%j~bD2h8E>N-BjH-bt~7r zC-m$&s#qLR%IOr%?bquPJ$#uwoO&3J_OR>nP}g6)?uk{C>>@eTuTh^mI?8`>^}cp} zHr7C`Ia0q~5ag9YsrSkL;;XICUm7Z|S^EvKrl^~b+*UiVN3B5qTD4~V%eoaH1+)7MO{-2Kq-gfV^UY5rE7C}DVGV5R3Di_ti9h@k7`+h33 zU1{c%JzQ~7>Uk;mT`lwF)Oyy_=P!d5ulOYSyefNWsm;IghdLn;D)7O*;N>=^D)yrP zNL1hVIe1vZE$aHGai#gBwBm~~?PS_+;~+<6wE-c{^B=t>W7;ZYc8J)zb3DY@I;$n7 z*kO&^IF**@JzMPx@~+9{E*q!igJUrt_QNx=p4dcLw#i?5CBh3qEoS^qqBZmBRrLhX z*ke*wqIFz`*A$=&iZTAK3{Z_Yv1Jo#_p$9$!|iP=64v#>k&gX9vUkgg=H4h`f~#0q zZYT3&3tH6DZ=t2n*wX8~Ih}|FJDX?U=~|x5H(-5HQ<;m8F-|{;Z+e|qx9au_P0kLd zdf9np92XUSdLLsP`$1g%YoP1E(k2{*H@pC*E9)q9D18+;r3-nk_9oM2?rqK~L1RDx2^ceLuNH&H#8&XOxt1DjYl zg*}7%oTYCtm^42QZl=-RXWr3zaX1da?Aa#aXEo@aB6nZ!yu;W)6kCz+q)Gfjnm(LJ z6L(Cm;xM6_=H>e6omerEVC(IR4C`6M=l#dR-hsswMt!8T&-MLNJ*GIOVEqE~5_4mZ zMDe(c54Sv-IR`EOOd|&Qrg>Yz{cE9kRbL)DyW5IPrnUU6M1}Z)i;kd{WHbgb6}n|+vhecQl)$ojP~&bk z+DwWistjT=^z1^tX?MNMqx^tc4{m(bT=$jGYhyF6t{w&44HoywbC{ewceX13lFI&c zz-uyc-OyFJj^^xWWmrm8zO!_EGOJW(#SrMb*ubOj$ae3ByHB>x9b73}F5#~HsOQza zIUO2@19;3htI@kSlS%)=a~}Ec7=jK}9v{Maz`!K^$OJGxz{k;ojWjhW`$Fi6(WYy@ z&}J15GKgZc66(NLSDLo{NuQOc^`~Ji5KHFd@$K@^3E#Tkl(Hjl#Q?6;ZjROrZ&$98 zmSzF}uOi>H;k_@_tyYfscZ5`8{k+DyW+mIDM3M|N;Dk{U)y=D4hQNdt4(qql7bhrI z?4ssDck=w*;ln(Nc2@{i{|o`yuM`RmV62JgOzsOZpS>5hrNNV+q!#Ka4|J3izgatf zD4zPl!Y^f`f3(4kMgi840xh#Q z<4yHj(RdUo(zl%0Aiskg4AT$37s>#F0TIlR^f>#3XahUW)Mnx`4)`c$Us4mTGFR&Q zchcrwd)1H1K4rwp`4!}87&7q1P|5*Rh-nTri|-e)NOOSY?N(-E)(DQ~fx2#^J;|=L zb9*-Cd|8*x)zxvM3#K26WKF@as*obA_7qgyvKpAonJv^&MW?YbDG{)sBOpZm^Pwr1 z_p^(Y5@Rh`I`UaQtnD)T%DY5RI`g`N*lhWrFV5C7*C*Xqtq5&a#iVq7iEZXGntd3^ ze~nx{!w$z5*VullKuO`L!8K@QV)&93XehsgP&M9onVB58wXrXQfCqyF83I1ww*`Tx zcXnGnnV6IBPB>X3)IGTsjao{JNR4cdOwEcul%p1|(5A{;I_~;m4MGKi8a&xgN`U;U zgFsA0OK!mmD(K`n8Xec{XT3$Fxrxrl+nVHnmQY+KI&h&-XbTLX>`ZS7N9`XX>AZ3+ z6|i~t#3g(3%HUwh)1k~iZmUH zZUDysg$IE|gAyVs&%>-$>*_+dQtnpex*YxRC^;WDFnEwj%Ln1f%N6-BoCo5?dS8v$ zY6q{gziPrO=GknKJQBP?GaSsm?{PQU`h&rU1WpHz4hr{gb33xNwiZR&lN5uaumIhLf|ZTxHAiw*z{Yck;k5qGcb;$i~F{fAoykIoZ|+n}4H zfcmX^#|Hbs#-jQWd-U_{QNhFB3ilHKJGp)4@eI1dQ0|J#ZZ-KAPgOtz`1a_n)zrM7 z4?I%Uv(;UE&Ys@}q=d<{_4Bsm^VjZ&f^ND)UsR@AeKPpzjh+^9&?oUPUyx8bd`(*J z-8kw_taNOND2D5YEfzbj=er(;13m1n?4njdmZ)O-<#=FmxR5(e+gacyi^XVv;I>rU zOef=jyB{~*_s1E$o-Aia2`mo$JJ^m^^EhN&N#bR3-M@_zup|=f?RW=kl_x23IQ_C-X%M0|c z>K!f5x3^tt=g-<*UF{Ptz9=QwR&nS;wm!FfF0UO#gpJK|K0Z$miR&)zwJZ3;e?FnP z{kpEQkv}E{5!&?hd^|MrcHG<8XAbYl$??8-ea$o9f7x8E5zlpgIeOaLqj;3^sZ{ou zt31n06lUUC|NRxVZ*d|!u%qVHdtf{F$f?P_+g=z|)@{Mm#I<(5cJCn7utqtXE3c^_ zXlNqFqHAO6=OLAEi~aa9O77X=qB-}iH-=WAJfe7R(aX#2`OWt%7j_r=j?n9)g~y&n z_R!(S`b#^a6_)OO2Ybi1hJu^rHQSX9{)8&`?!AJ4WoRdP@CE7$LP zlS=s$AQ5)yt;9 zRio)T?8Jrpz{xMMOHhX;f$&S~#wQkT0-EL${?YQcz#kl41+avF+MXs@0ti&{q9|Bh zIsRwww-R{>Re9+a(Ai8sk0n~ zml2R^Tl~W#q}FNaK4&#EPmb>&(eCR98QG*x>=>E;!IP}%Z&=Fs#a210#J>}+Ew2g zoPgw4GKG3D@}8@hF9V(r*BtG1#iN!4DxhjbuwqERgJmpm;-Z|ZPNrPd9CZ|ivma|1 z|1>%9G-$sz^qa|>m@|a$Of9?a5@o$f2BTUjRJxhEy#-l}rf%UfOEv$w&25LnQl!%H z0p~VZ$I&WoyX|9xe2y+}GaqWi9*++X^MMapot@9A3Mh&z%r(Iy^JqVm8kpwqi(V^W z=`#A~3n+f2RKTLt^2?82KN!+n9h8p>>dJy#fslC#hznwb25Z@Ls%s-idGxtFMVL+|`|6pbrcCdOS5(PihMu*O5TOOLW zpLsuurYsrjNsNXW20vH{JTi`U$tag#(r9pY%+aiRp>@_saDAItPBI)b%dW{I&`q2z z`n>&v(T7kM*KW&5C(S0231|&ELQx>AF*$b~3yI&YarvIVLBE7vVhBD{^%aQQk+h=e zv@+mMKJ44GKV*Lz8VOF0Fr4!C2TMToGpR-&^DA^@hG&%IEt8HiBv9s!6T$s2xd#Nt9fmd9b9}-B@m)u0F&c*JJrko%(Ahk2<0pyN1Y2Sa!ov1&> zs~~q`5*i&3ui%-XWvMPx`49`CpVT15;xte}r+)_B*J8&@1jZ?}*i??ul}a^K)@gB! zzj^Z}6`@Ntc_2O9XLAtW5+g-aa&H&fXb7i+2@@t=?6*Qfo;JC{f9XR+g+J0~-`P4# zB!@1I%CnjUi-sH_8Fay9_I8)I2Afgq$7|ghJp95>Q@~^Z(fB40X~D*$Yh9aNz0?I# z+E}NArJw=%w@@#0u&TnHg>IBrFH)>i@!#akm0+T@bmov3hw**W`r{BU#zWQAhV5Yw z{z*ecMdZ-5Mc0aQIHV|%fa30I5OccSFOOPA;D3TLc#^g#5AMA^~QK(yBEf-U)xeDN7ShYyRlH@BuU14nWX7$E;j z41Ts9__vZZ=PQ%d9i!27pih+HrQJ`>CbN;!Ri{x4bh zds{i9rs*~}ZH}kw8#5DK{Ntij-^u{uyHv2e0SFfOyvYf^K~@^~n>_jdi#!P<`1W(> zfRBKlUmcZxIugqbz7y(ivdmu8yRXLft6XM6k3ZpjA7H73@k=WrDR!L2gqMOhH4T~W zQ9)sZelY8%8%&C#0q=(bq~8XbAz!dC0GT$_p!7pa<_{WAY4AF7t5i?&O%xPMgt@aH z?(Yx0mAND)4|12NcT(3DiK81(G3~8H2k|8-C=d1Z?n@^2f9=$}=7^6|9pS!fWx@MA zSE+wiO@tF;NF6kT@!={~SJ!S|h%mzjk{R!CbxsucfCIBB%*<_hXx{$BeFlR{r}0dp z70)R$Ap#0?s9LVDw7bryD~rDMCHpTGzna!90XXnURXB#e#J1jzf8u(d_V9gZ4DS$- zx&~Vi8fl=vsmmi00IHHn7asOiiA4m24Xb^g*YLZ%AjZFWt1*`1ZnstNwC#`hzKd?N zAG(!ck;1g~N83SQP~oyNx!ijd+~jQ`csj4oIu?KvWA1bj>%qe=1w7?Of3H zSml*C61g3wW)>+m5HIwoq8>45Kx)8NLN0(Gl?ORD&c55YC(S1< zb8*v4Sm7$yC-UxmA$%F@*gIN_jy&rC9d1uP^n`Zlj|>Ln#$@#PzP6fo#pY(}+xYla zy=T)*Jhz!5tR}X8Z*%6lBo93AewRq+U$|iewG{{D#e`D+he^ecm`Ni{RCU_CQX-%D6+Tj;C&5X1jV0a9>m?F? zl%vz}l8SyU`fn@v&sRu}Ze6>=Nq^E?B$_5+LlsWDxqXGZ_TFhUs_+Z27%*`^h=UPOKz^kHVYaDg*_qZNmMbvfOz;=29zkJr5g$bUzQ*J(1>U}9k^Z|{npV3V z%He87a;NT5m+dNP-p0ezbMZ^XG+#%1y9HtN0(Tq=tib_{1{b?yIFpcj5k>2ZCTA_n zem`6-lm-MXc#7>iT7s_G=!Vu*^ReTInRAEC;V0gu5n3XK-wy^KJO5K5A1T)#G5hxK z>Zd!q5?`iX-IZjP^v42nqqEH^POQbKO!}0Je0gZ|eh_>k^fH9Cf3xj7>&3^eip#=K zQ(~LTin#anFd{l|=-HX*Lk-`eKuRA!=BS~fPhVh!!HGb9gh1cm`B=T~6Jx_)*>uY; zmXu2JE&p>)JZ9Kxj~c={vEFz1tC0AawsZ>~y6}X0k-{fc-a-+1*$?D#7!vS>P)h4; zv5$&rR;CRS=^GG%Js_~5V87`$PAi_gbgW_#1D*;(hU%}d6n^Nva0XBe5O}cicrCeZ z5N*6Gm*P;B;E}V2id^if(Iu3NMRFEDD~M_H<+o`9MXBMGF>2v7plJVk*V?WQ9vET*pncd@wgPC4aUuBlySebzF_4=eYaEBFj5%U+{ZaAzAurt*`s(j>(I!{}-k~|$tP@PRMI@)o zTBxd^icH^Mu=_nPe3Gkw4Bu??M$5!9NlG9VF)2R0wv>oH3imBano4aje6%Evd)SzgM zL+c%CJ6|`2&fHm4o|rPU4ip^(F3=57b!c0pcWkb>W0MjQbq}8I#H&#m9KfT-SqS(^f+?cayhXopgT%2RN#SY0o0c&_w#xcgi3OBUSehvqg^R`==J?Y!|IgQ zl~r@vHzr{eK~?oQrV+_$?=Ze9XmwJDZy^=WWLVe4PmnS$5`x7>!L7EhgC_4Q#@-j_auY|KOZZ5o+u{~S;uic+f|hut z#oSPWrsAp%sRi-bTU*{bpn7oOWsT|!7=As+q&x;pK}u&U1G$`d_d}5bWoLw3A#jC3 z0gn<1D~CzeeMXoCb>Lq0`;;c6Qc6V9)ZY@o2~G8H z&WU>1a}oJZ4bncnR~afR(jVB#Nh+SS;Uh2Q(lt!oq<;2p-p2$=2om$+lQD%GO#TG`g7#5}2cKd!`P*rw;AOA4CHHSM`@Y z)ceu&EJr2>j*bLR;jPR5_vB$Q#1)cNS3q{r3}dFbRZ?>t98S*q!m z@eMO4m3^!Y4B+RWcS%<|nTQgY)x3gZGT2U9yh$P@Qh=}emx03hx|6ydZ|x?ZD6;oW zh0{7v3V9dCpdBo&dZM-rYiXzE5S*f28{%L09N_L?4D9{uOY0Z9;F`M&Jg8LI z072IrV=4kEAOAEODoQY&Tn0-1e?;rsJAeIChZl$Jf9szvLm@?V1r!&Hm!}66irvyj zsHbQ`^FKR%gZj(W0q$S{9>L~OLSSxBFJG{PT?Br>7JKhmuPjn_G@{+$en}PPd$xY% zGP$j`ZMV&)bog*-SFq4{tv&LPc<_iC^IleeLr#qdN$xj2;FIpC@La5|K3LNTO>LW+ z?LW2ruh=1nja^|znI#rT8;M2E&I|D5BEcfiEV!liHDcLlmTkR0Z>b2Z5^^MMd-F^5#(*@n`i3rMY=kSr|25=msrwbxTapi43@8Nsc@ehZ&(PuZ(g<5YDu9lm(Mb&NbY3YIgNXtuQa z$VnTu;_miD^kE)lyV2WJ>Mylx1QY#7xt2@=%GJ;5uX3fzpS#|vTI#&V8tiek_2AyK zudr{mC!lhEc5hV#Ek9J5JpcP_3Z~=_?mx^fwIESijEsu8X{O|`kAW)@;%zYX2JYOJ zuLLCJr+HBJ)JgRB&WB)CQlOJVIAD`U6IGHqe?B{2f5rGWV0V9)v70f$_JI1LON`GC^^~o5>hsZS0zBxei5N{xW=rV-D}cZ;(4j)0%VqC`8-9 zoDv+DZYcPGjcNc;sN`g6Y&n#Ph_}?k!PYz;Ua~qC_JgxBaM1r|+Pxwi@ztNZtKhm( z>4!TvLL3W^Ho#9`smUs5XtMh- zL%dgarK2IPkJMghSpnjhxwtxurHsew`_4w@PacllwFA(zJ3O|jcO^cX7@3V4u+jM+ zPFA)AY4cXz$o$`#sMF69FN4``g_IYYkIjs(Ezo(GL2p zZh@h->ab0o?M0NSWNjAdE`>Jp?ADMQr|6&aY%l;Yxc>I{Ny2mgtHMn*6@CAQN6CXz zx6v)4L~Jrg9hl5@hadiXiv5Sf?*K$X_y-1G32HmqD-_U%MvqA{TLj4o$A0)=WM1b& zk#wr`*AyH0gWRXu_shT15rpwD<#uY}sp9ZaybeHsk*9q_R(}n6rC_I-0wTlY|Es$n zKlmL^*VA#>B;4P=@UmPS)po{)g&0uZd{!R5 zANpx-@09&YJN$Zac2;QXYwdG|0Nd-ddVu0^<#AP51y8S55KXfLFXSYfnhY@a|CnXN z%G6GBNisR@8UN=b+x;6@+y9SlYf*3gKHU7hb1*}z77v#2f4G(A|KRW6^@Wp#O#^Nv zK?!8sJ|7IwWq*&chm2*KTpl=@pDKa-Sz=l12GEAqI|=va1%Ag+{;hEv!Jyt@WQs!B zTU-)oG0uN{5yk)@qP$fr%=V!+b zMeVku@`|Vq$3sqRidr?x=V$lk=7)>Z(XM;??<%)*(iI+Q$1(5HVq0+r1|Z8^oZ7?= zkIGyc+Fg=qG;KWwWu9$7IWc|5?0gfvrRK?~m;`0?MUobG97`b6_FL|jGUXX5x%tR5 zQ72RZuaR$0W5PJWz3R(S^Y@-F9<$wB>kaG62@r(s;CvFSQrLWU=J!OOa-(Y%_*P2r z3A43H-ynNI@qrPx^Yhi7rnFhqIxbO*%mbaQRHWXHD)#C2y_$@!v%>yTDt`dQd$T+l zVsw0k2$-A6cPM}$2SfLPStmoMd-|?>KJx47>IjA!6gj~sXsV9VE6n_;cES*0@L7#V zP|&e|J|HN)t^2oeQx#8pLD)MW>^^u_82QRv=6e6dTnJy8iyiMD=&R}C_rY1y%O6&6 z!yqO$OJLq;i?Nr&kop%&(J%F#!e4I?5wua!0TcLFEdyYc5mo+z*WHj$oNhe4F&Fw} z@1&L{b<`eE^{{9%ODC2;hRuyql97?I){rN9{j1*7sN|crgSIQn*hdKieKbK z#{cOLY!hwiNTnJ*xj6o;w8~F6EN_P$ikuncwB!EZ%Yk~u(Otp6}&D7Pj zQ7c&fsL4~_+X+mAf5&~mlrgM!lwGTAWH_3BVcU|+oz!thenY3@X%{foH_uzECjZ1j zWENLPAwN>rg!%_OrZFYeW>OC0729fZ%sd82CH)PLG<>5D73vIOZZ!CtC#YBkRkMm&t!FW8{d&_EBK|d|2f~|5;&a=OmX;S5AZ^mcC9OV z9}|yG0Tr_TzY;!VO#~M#$(ph_-z+W?JIJ|&Vw*pTWc+_%QJd#@{9d}Bb)3L3$2u7j z(vhnLvB+k9b?wN&|48`gc3QoKtGIDh-uoX^3L-AuR)&)0OMWjx?oq9aOfW+3?`+Sj z5=n)K`=@-5OJd%aydOU|{KLe(CUoB>`^o&o^IKAijbiF~EOkK&`L}qFJDxy82H(Wb z#W!fqOphycD0Q1Q?JAmA~JmfiUx&CDOtm9 zr!iVWG5|PL-Fw|a0{@v46=q8f_{jamiS{o4jS~g)*@qUoyidw1{I{x1XMA<-wx@I& zW&U*Sf{MP0iDypmI@rI>zVO&C5gBT+0)ri>NpN6@(`wC z(S2XnlH&~z1tlkwuZ~qwy^|`*N})}=lPisUSci3)bs41%j?_>SacH2$clF{G3~NXf zZ|w{6vDKX8c*KcoQR4kbbwyQB;w37#`4@IUEgh?MNTcG09R?MK?4jggbWslciqt}c zrl^l{;BXdE$pq+#B%n~c{TPTwz;xqrbSvhZ#WD>`4SD2o_()}vLbIa90>#9F%kr@; zuniF64E@wW;(!Ct3LtUNg{A(!3F(`p89R@2iqS})x5LQSSr6_i<@nT0~@tG~$l>33&WQqaJJc4tFl*!PqVzQx|# z*ZX8b%#%-huUJIFSoc9RC&_c6#bi~Ppj)hCrwKD}pp&8hVLGkV@aN}j9u*JIQ}&;R zlXjdOu`dEW6Td83X*4;AKE@veq#5xUbGhOO^HH+E!GG$RU|>nj+}I?TjCsQn>xOFU@3Rw_FQ6Nr+Q~_F%PCoCVk%0RIZHPP%8xGSd zsdfM-IKgA?Xi@C;>>{97TxS0gB!ohtnNx6vttkC)#faP%hDUIQ5)H;*AWMb-E!m(W;={63vWn>ZS+-H(1l{1&BM<7C+F3la-)&uKQp zmMh`A=~`Sb#%bn!JO2DM z2IFiqE@>F2A+Wj)*l<#U^GH2g=bB0{5v^F4KKunlxP+eRn4Q0VGK58n!ZOt6mQdx& zE;Egy={hM7;=>{9HK{U?KvdE80CTTToeT|5qhBxrI(#FzaNwLiTe0!btly-&VtMec zm7PoKjdGBGJFW5Pc;JqgnrfJLZG~;xx!H*n{ywIG^q%*vAgT}zWRGlH3gU@HfG#h> zAnDJNQqv3?vhf=0I;*J0ULNE$%3tR_V{|0V(Sn!|T|&WP$}u9-MTSQtLc#IO&!oTQH6w8bP>slT%wAy{G=S3_kh=eKiRrYT~93Y8fr)<6tMvp?T4rm5*1T+ z6=6107p;-IUjaCeHpX_JW!*yjN)R-X`hwNi>Zt~h@NR}uk^)8D9bHL6lce+Aw{jG6?Zlu< zjF>v%=eM#)0i=Am?$~mNMm=UW) zO|q*LxeD^93SA%8@fXU}Pr`*AEnMfG*9B97D&0>N%A=mj!HqR3QoE#b#Sr>H_PcI@ z@=ex}Wj_wV`(_7bOGUt9pRC=kN?kej72dCdkpL|)> zQ2HcNyH3;bvVG>e^S{e?l}u3HI0VPSK&l5FEEXthhTkPTET$Clb+R#VOLB0T!+q;h zTv5?fd^tu`7FBYRQPfb2(X~P_pjIgNl%qOW)PDHu`r*Ce zyhm4Nq>s1GpEgM;>|EXop;2kRBlg8=7Oh1?$1#x33gY~ua)AQ&ESuO`$GW_=2kE0B zcWa8K&nuBK`0A&kmb{+UP`xKE7~P%li_{HTyE|dnJ?RwAN7&$26V1pCF;_p?0hGbW zr9ZY@CMrvPrh1CSh|9LWl=vfRzaJw|wai=nG|JNM8Mp154>&LM5kj;~V)T<6Q*ocy zmyUItD+jMk!h5_2QlC)sTota4>33lCXW5qZOS>he6KgXtLbh6x$*y0n?YZ0Zr#49+ zmRET$1RPi$?n?`+sBBMEWpy#*4o)8(h&)I_6k)Q)GMol4c(y4u7m0gjBzP2^udB(W zd#wc0zmac4J9|($pRXg_GP)pId-ta(MHMnz>)#2**2JCORct~~dT3FKDaQ`}ScPDn zq7g$gk`4)~@k1jL5$=WCK2T`rHHtB zYG|@(6oU2`#REf3f`SGH&d)FNt48+M>K;6KjIVx@t!%fyXQ%%Z`WvR;ZZ3!DY*@%7 zTU~hFFe@SrD9mnuY)7IOA@UmdI<11fmQ8^La|mD5N!DJgMz@4n`6o1AErxv-gJkAZ zUV5($*G`#c6K=BEK9~dm)e?BHh=;6dbiO7&Dk=hy_R1pePIqJn%Gk?&zo#ZhX?(Jk z7{7UOoI;I9xcI!c*@S;5<1EhMbr|lEaf7`)YH`E+*j7H#2$gVJ4U~1epM{gF`YEez zU4P~@!80^r`@th$0QGk(T^Y65EW0>k2~9;wuD;{&9(v#kglrgg(SZK?QQmayUN1PJ zPlKM4L-&SrSKbj&&XKbAQuNFcbY|N0k1?y8Tl}XhgT{eag$Ck%~hvU!UI?}(kDZynT9RSswgkcyfXD2k4hTZ;+1^- z<{)I5)ffmUFq6P1igx5KI%L&Pzh$g9*_%ujXb;{072De9LtGnFpgPjcplk$mofZ=4N z-Htm=F*H(s;fC8aOPH73S1g=>fGpjwANYIv_&*$m{s)X56v;I&@x=?X^ohyGY5gaN zB~hgf{EcFbUc_*u+8(9$4VK!oWu>?p7`!}Hb1Y1v>YTJ0hvqGskNj%Ji7B_{q>Kx1C zNbG}p;>Wci)p#-8G<}2EVX0DQ;*@Fk@@Qdw-lCsX?&tcwOtDZ*Ey3(!XI`kbm= z+Xcyp>?3jdA!PN09m(`wOfCkfRZMScS`!_!`E~{G&(p zEROuWhnFMihwdre=lxXP<<<8DG?tWSPkX5Ahy#z7iTC?jt4eM!hN1fjk1uzKo<=y3 zTNRG#@4|Vtcl6J0Q44lV!ubn$A5p_3j%a-1sVA7&ifO0hngu?x$5nAQ7KEu%z@g?6ne{64Y(lfrrh zalJRJtYLg{#o~KlI86^XHa0fSL~JNG?)2;}#bs3raG|Z&SFkV&bY~!oMn{e_@o; z-hW`!;-MpMab6+G=>2NWv7(e2;s2INf1{MyY@IiQAyU3sT~iYStw{l=dQ#g`Fub1z z08oG66m&NYYqwU}MJ(U2%CJj5iLiO&#( z%5;M;D_7Fsh^lEAwV3!kh1?^oY>9(4PIz_s<+{^i)y9&g*><_cA^0M#C%aljp}u<+ zLe@?(X*|ESUg(DD%OqJhBLGsrv1$@5v{|=d#?=3RvaQX}A73gRXPYZN`o(z++tQ0g zdh3SW6mU5YFGUJIjGjM8_{HU0OB{a#5btM+6{RzxmdX zVX+fT<^PmQ^-FsiS@q_NN+B?`Uta0fDuX{t4>w?uxU_Cldh7BA!7h*^$58(cN{!Cj7tBO;DH-#koy3&lIlxf^NG3 z*z)YGLa+4eFVR$ynH#kT(XiHtko>t!sbLy+!M$xd?J|>*|JkTRVpMTV3el}G88PS6 zbT(ohHJzZHB=gtgb!%;d@xY*3Kg5ksw@4rT5PebC9$M#uqTJ_GEEUj)tL>?lTi>_d zmTSe9dm(MSd@(j2BiAw-4-E=lVW7u>sWZpb^26NdTiIkqq;H=J{U6LG@s-)kul&Jm z-t?FOvr97CZkgt0-d?#P!va;+>U0jQsnfjvjT2b7w0jI4ig$3Ms6wl<$L~&%V+b0v zalMFips;o^D(*(Qe>qNAfA)QQ3N@A=xbv`Pi@5br;1;Xr==kX6_F_423j@A7YRNeN zUR{l!+U!hfajDu|4~=DZ#w=1YS~tSQ?PmDF!)ms<+0^nhP??>)t>EE);sDNPK4f*AHNIFtygaw)vfM3au+^(6O>aUP zus4*Kb}m%YrfDt4LWGDu-`#X%Xaf%f3$u)Lag#wv?vY|&I0?ZPU9*G_xFPZkzau+) zX_Y^P^wlMAY$?0>GBAk9|BFp{XK*eKjK&0OM;b8t}nl4B2*enCHXXj(*?{;np&8jD_fw+TA=I1H%+lY^9?Yu(ROrqfu(q#EkyCpIG- zY8I8cO}t7F&r~*7JuUxtj01*R!D-=XAeapB}sjPM;`k+i7_4mG$>x_G)@N9veMAX7m?to>FQA z?QM9jCn{2nky5K{-nIvCRfON2K7JV1_rSYjhstAn)n6)9qg6 zz}|Y`_&}KyenJMZ(S{u|7P;62oefo<#CXY@12HHixHcI}c)mfoYG@zZV2?TScL zE~#hXRxafENq*~_G5e96i-7`ym@S2uee3fho%81NBTJjrkga#p>&lPr@t75kMXl@~ z=`zweAFp=vi`Vec;tB}u5=~zw=N}|ADo(aJKO*n#=&ro%&h*cL@@65<_d6M^DaYw= zjs3{*kZy*bv`pNzrrJE+e38oNICEQDxO|j|v~}_5dq^c9=S853IFiBJ+rI6)OXeQi zzP&rjl3ovV>(KOf*M~kOIBUn2UhjB(>W@@zJ&{sv?YMj1+dM;9dVKuVab$P?Y{K1v zV5o#T@co7>>C&-qWP{^|=z^@cmg#A?4ujN zm_+hcjC8wf-RTIGKCyLNdv-woh<0bXU`lY-QLXOzIE(|y9;sYwtf?7(cYzlQiTdeO z^veQ#>iv)K$Y1o&xiRmZKNjB_2na0G=c7?|AYRU+eY{@iWiu0iAHBb=q*K+#oNyw= zp${99#aRv^eiv@O#-(QL(R9sY%km7XU2cvPPPv-jENPeR)1S%sH5Fhp%;H-OawQl+ zp?x0`AT-z^IQu;Z-N!O*m-%`}`iOL7~4PV60 zP*zFKR!`?_8xq;Z8idxAD$`L=GLy0j<;ALGl2Aa40qE*1!-O%a1&`B8M8I~dRg8YK zu~M(-lXIwFFc6~ez~~^L#Et%fkZ8)-zrqJbCTJmab?~Fr+1-BaohC|(hR1SC%dG=5 z_XmRO@{qudox6=6nfdFQt7}yd`mSM$iN+@MH;$~=#p<%U@W_0!W|g5KJuhv#%I4Oo z7^d}Ubj!3=)VmrrtGLK2UCXBnI}1(veM}|HYjn@(H+$m)H@LwLWFYTyHOhaTgY?u< zH#z8vXj+w#e#&YtG-XXWk4Mu?X%iqev;Ik|0XyGaXGN=#!av`B3Bf!^6;*{@>~E5_ zXAV|TWzlJQG2n-6K3tUoGN9BP)I#^Wtea0Y$=R^~X9yUn|8v9_#6 zia<|2$*Ghv817qq{8;n2Rw>$PCSw!e*W5waUGNu#yj}Idis!!wxo?BH6UV{` zowLRzJZ+bil0dojr!5=BM;7&k!dVwD*uTeE#!`m``&oysJ7E@Y7>+Ks94fYitG6xN zsj8f;gq4Tfd!1^3IS$~U@$_8xXd32EX*;}b+1mJw60)^eQ5kg`kZws3^zc*i{r+loXk}h|0j;GyIhevi89Wd1t#!ZV)GG9KwiFe z=hGIRyb<4j>)5$!YDF&Xk)F{w9b{NY*-!iYVG!>G;;PC*R~{IUSt% zanhy-THbf(8)B{RqJ{y+0Y5|;>~r699)jP!J=NEGRt90YV-S$@G17N)l1qMp{jU40 zr{H12i??(46qAX~QK`%?KkHanpM$f^IcXOMlI%s6`CFghvS>GRHY{(v^TxeaOS=}G zfVTkkcthUGTqMaElgOyu^x{%90sqWy9G}<0`_VHUitvJVP=l#Z@S7-$!1iFm`-r&( zS*Ll^!Vq&)VHba!A*Ji{9fhOFS!cs*L^U1?)S<^bCXUdscWIF+GxhMJV9Z?-49u^_ zjh0coOY8E!gV02}Jm&_YCpVNynnT6RLsP1=*ihTjWY#IBv^X}L@tG_z3<=|ZNmqRk75(%*~yQEGibx9 z1Q?bzpJxaNse)j#eF}l)o=A|^u9==^GpsPGtb`l|hdn@;U%o!D6;u3pIcw6)L?NPL zXOC5^D0}IEww8nDnyFwi`E9sB4z-EG-V@tol^`CDzi`%MLc2w9o0QR$=tz1+`v8@E zfp$wmS`0x0yUFrH;ep&YTT&Z3I0ikm!o8tw7C8Sy zB=TNp>sMYmPCa&T{)>tr(-Y*}3-Mi@to~HbK;vEW>b$y#!2u^fRN;1eSQuGoYpzW$ zLh@c-`B8vj?%=TOwNvoveAjTCv$0LQEC}G5{xY;UOMYo>M_UhXA;N{P_;M6b+=^QS zf7B!EL$BTNpw_OSBmUhS`<<9xV=DZ&sUK5>I4>_~*3KC`8k$Fu7}B zG*aAxpX>DrG^{=6(R=?SxVw3?wLSzn`=ea;G`Mlda6N?lv=*O)6@EHSPMwL(hDJ>;A!mOxloa}O z+FqP}x@P-?&^2DLkd&sWnN;Yw4*u1(H~9Tk;<-&CR*o{Q<)I+u8Rr77x)_pTD;6A| z%u<@m3vt{iTj<~Ch>j#w_o<=8AuTgZT^e4;pKB1#0pp+51kOpbQvASIGv*w#ecR0? zj}G(3kCr!>_WgU&k@#wV;D4w%S>+z_-5e8n)B!nEudy1B!AP-7rz^gIQZI3f|A^TX zfZ3lQ_eB>NA_32Pu@^t~Z_VD|c3u~C#(#ihJvua*=TCqgC~CN)KTO>d11^b` z@r3)_;}x?ZXT)y9l#_|1lJS z4B_avyEhP@@M4TJ^R%bz#XP!EOGUhha(l*b+?XX)`2yCLP}R#epO#i~y_4pyMcm?x z&HD#N2e!pqn-{%v(H(``C>#9fX}XKd=KipSGU>AUk>bLq$Lo~J}PIP}zvxXUcOcq78#Uh(sT{ak=)&mDjScaynQAV7;B zJq{r`p0-?1Igo!+D z;N}35A$r7IJ^H}?iJ379HlyoH#?Y5wdD*-*nbxtM)SH!@+haBct0J0m~5U&$noort3Gig*Dxu z)3B;BJ|4=5N4Kie6WB{g61f$MpXQ83UW~ZUaI`nImt9JwnY^eS+4tw+)~pSDez*AX zaDlwIEytK^W{J%_G!ZSsZtgBOhb?(2!YDcXUH3W-SAAY_$HKD3OkJ8wjqb_5OV85A z=Zli}cUJ|4eQmgst4qelMy3PXfvr8`HEa#+A8j|7=L6h!ldqaizB2sKoLaoByggmD znkj|-HlowI+#lu6W-XEjB;udM)XKh7Od2$XkGzuGls)Gd1 zJ6s`k180a{0I0kDDxn~KW~8^f3v4DBi!2%5n77vLuyx#a33O9mTXm1vxhqr&mvpPj zyqQ08FTy2uVpsT-q`MD#_K`=;_Vb22Swo3vP`s90jVrmUQ-xFKgYd?x9C|oTVF`Gf zv^41@LYrmlS|8nF9oMp}!^7u(1HGTI-J1bnG3nC(0SGw-*u_5ssHvP zhR9P@=5OtwdK#taf78Xk(5U|yC_o|t;x)cYJfahc@%O~{utt30RGxOOcYOis@> z^pzL#)yzyk`W7fZWwlap-EpPms`%Y`oT~3Gh$2_C>Kh@OG~a{S&@F@!nKBss z$WL9V6-tCq7jsfI9SC7bh#Ep$VjA9c{gWrIF($6;*~RAL^F_AOFNa;{@uFHt2` z#5A(GjviOtKpca+c4+}e!CiM)aXm+X;d}@+Fq}8mgf9g*wY8p0o~oLgGglX9T;v-V z!Z$?@fSvQH)1g&?of9)-J2Nwv@;Xr^$0CPyTtqnPV|dfjX+amEX>7vt*h|;3DZxBC zqV|rJF$-q9zvYHHa5QegY;zKl-hFPQ9sr71aHC*$=|?Q}I$IN}@aBe>pWds@J8NI< z_UgaP9R_WU%GvkjO?%Q!^FK8@da zklqVm6G8;N)$_t4j9)NAMk06#=ozT!Vc7NHU!5(a)};L)KW$Du+vn7GEQ={SkV#hv zd+5`gS>9`uBFp|d?t%itsKQ+bZJNzrebbmi?3hOl`{mFtSu8p9*r8@Xv-jKr!?8$A z0d&dVp`ZW-_{lEOvU6B;YFpE$LC;)if=oe!d^iim%>HFJ|6(We z*UyqE-1U$?YuBHTPfu5NMH(KCyq>?TJp1VuCg;a07YMXIm>YR4DjznOET_=ui7Qm1 z9j2uVd~hHg5dJ)YrJqNAVRRUuS7OXmAJ5T$7__j;(l0m50QUhSVzjFYsNjfIhJpKM z-32(4RR$tWJuLf{oq6*DJX8Bq2q50UnR`EQ+#0nTdN)JfX zprJfnpzOR{coAgYAQgOVHI5UVtJq|V=V-%Jxo@Y5s|ZLgQd28N&D~qnr|eeilB-0c z0T=I@;>AoUGOdBPXtRFD5~)^ZQGi+b;8MVKFsv`}H0qJ0R>aE1lX=)8qu6fqHD5>~ zdKd1n;aC=(s3=8PQIb`nH`k{afU;trV(i>Q=u=4T;n~4<)2KL{VquzL2+bPd`N7O4 z5oKcS?@Z#%X0(#cS_Tp25ZyBD;pLAs#hm*v&AWW_gLRP27Q~vSK*A6a=Ffy-Oh~Xj zkT5Lpqy3#Q>@37)HZiaZ!&YoR)T}QCQX8`^<<(yl1VPNc$+9y3pq!bZ41>wp)tLd_ zEaw?I=Xp%y@4oJ~?s{@``{V>U&K``@*j{n}@#v;yJg1qWO|ksO>5Ieg=`cZfbXwE- z+wyG2&4({(T~-RGyTAEyq`HIX1C@7K%x1kaTR8?DiueiHJv;b;$HF3b$a=))trgy< zDm!ol8ljvSHaS6Sc3X;iWZOzgid`{T3IcWXHE)zmEQ+zWeZbkJSPKhYZc(X})Yx{nBn59U=gx;u->p~#%2XbE*{anKe50G!UM)ABG zkXqh=_;wO9K*51{zVUw1!JyU5|KQ&n`S#b>Wc)a~D2xubLi5}Yqz|iu>7S!SVDbGw zZ^$~K;Xffo5+`6-;T)*1ffr2WO;;C*+Uper$uqlt5JIB0f=LA_=%lo|hs0z#5GlAy zo(3&$hgHlIFEBK1MU|OK{ajc5S<$cFr#O?U5C+&DD6&^uQ^p4~l@b$I%#(^SbZPNr z)Byrhxg}{l%?Q{1KIK=SdFE^Bb?u63niGmr&4*mEX3K7bxiesz{PTGGI8E1l%Gh$~ zG;TFzQqS^hx^L%HQ+O)fm2^hR(q}?htKJ-)w=S8SC=HBksZ&4hO(xI}6nt8qgbmy< zG`?I0p5S@&7|$D_^40!(D#mhIC7Er1b`gVW!@#Y%R^@FHW&jGoR#>W1gRM9ct*iq+AfZX>=|RxM8M^|6$sIRg%_;oHBSeb zpCk|T?z^j2Nw`@VbC?Jj&!N_3IIwNUW!~HQl8Q)FUu8Ql9>* zFS)AYx1zz`_8wdGwQ4PH&d8{R_YGo_<1FZgp@(Fva*7R=ecT=lA$b(NuhH0ZM68D* z>=Lm770qdyRk5fl%p`r9Q2$&l-zqs~rJA6({XSLW1t?+Q`pkU`HVYCWJm`zfbG0Q9 z`9>*4lW&>&6^P%-!;34jkP78%8ATRVF(@&hOMuIFDFDk8R6f3+`NtW@an7y}s-?pY zD)dHgoStT!@}@RsH#J+5cB@}pu7BacT7qkNEB`=u0+#v6PSYafYm>ABusR^~v{n4J z!o#G(WXgGVPQfG>dueGfU0FY?>{s!&R$Z`#yc4?CXjchP%UYv8%D^0$NZkaO17nv) zqu#l04lER>$;%thv^`lO9?#L<2*$Zn8tTvdsITvPEH ztLm5gPAD^Aj)2>CX)q{*q};XRI*7L^VI`aZq1lB$L$lD06dv0bh_1?I4~L&r4jQ;` zvM;f)`kg$RWBym}ZpeT#8T_j_sQLF0Q|qs`hSWyMrXxPb{J32$UTm50UqP3=>cQCl z>gE=yOF%cz0asCmC^55dwu_SsT{4C^uCInv0y_pNeM8*|QwdUvvT%%DI=1EUt5KdJ zBi3kF!^(6D-_+nqF5OZcn%86P+traRsB&|$PH}$)E%hk&qQkXUD3taa%uaBtw;FGN zuO;N=`iQ%R-+|DOm;$cpDXDy5;$IYyy+|e+RX(4TQxaOJIe~5muF0=Fp$L*Px}W?z zeAlVnYwCu$4FamIyi-D?xyUe^3jrw~;`Y-ZvI2^Bfuvn+xxTyvN6wqS0%$;O0u^!x zv+v*`TE#h2@Z(Qi$2H$|XtU$B=vrcN!wvIWZeM*M^7Tuu?^v{qt24s1_*sSKC){@E zKw(<*M9y}i7|X3+o@U8=%%*U)xqWW@RiQ58+I_tZL%cgAV(v?Weuam_2L>ri(JH6O z`2|N0N2BYC;aPGQvkZ)+{70-tQ1bD^0tEdL%E~FbS+%>pbJl{}9J%L-I{jfSG!Z9o zX!?jQMksORNpGZZwfJ~^KOk4;%OHTn2G!kMEonE(MDq?Mz|Z!Z)%KFF-;1B$i_IOM z73NCt5^T5&`zw>zZDyC$sD2Z9(gmG`eCU0CyA1qho>j04iMwaShU(4m`v*I)lKYd&(;^l2l`wxVMZ8Tfs@ zTv?H`4JX+gM=ZppuBJfQZ$hAAg8C8$I2R~~ti~r->|DSLN6S}K+AAf-Y8aBtCvk?- z6+y16ENzGJ{4~J3j`CLk#pJST(3VaP0cPi`Bv7=feB2VR%Yo;$Fz7stp!dG#3tW`~ zXYW$67#yomFK5!H(!gKJgcw2KdfqC8N&>wIJZc z%9xWagJ*yk2P48?e?tWzW^of$${SW#YwZ}-u&x#4s@d`)Y&M%8Lk+$#eG{(snTL^- zt=76G2^~}s!HD)ZAW{{%)+bfppWDm4)SKIPPz@RP$;TCxm*-42Ngk^xK{Hkx#FK;# zas-#;m!Cjm0hi>B&96JMI$mSjV$(GHmRCz-)y)5)F1M%10*7VRd0Ow)mJ z-`_UVczX#{l06_lnPd4^!-)yF9>={>7t=BA=$5bl4)%}|zv4xNd2{no^2@CUYhz@K zy@!7O#66H?oGJHOTCi#kQWI}JRGq1o;F(2Nt~<`curB7Km4TfBspBhq)dtiT{je<1~M zV0(7O$)DxEICvbXO*iAHJcz2|V$0~4#+7R%2ulnT8kZw+4ga4-V3L^Qo8uvG%>x)$ z<77SLPvXcedy74E-L`+KKX1+8(cOnU;e`_Wbd*?JQ`MSWOgGr|-Ocv6@*fHL7wJ7R z@|Qn_cHLS+Co}-%_gere>D2^(CLmF$3^#l7H%*%&D$#0f?NyHK;Y;m((f#jQ@I}&& zNd*pLyhf$@P&Zjuj%U_*efYQvE2~9^5qRCVPp1kv3GVF2@h)WybX$D^GgfXA>X#*Q zqA0eqkAr)_Oj7M;Hn4Ka()mQ_fRQ2W?AeaoQuQI~Ylo`G%l9qF;pO4%10d}90zUHg zV!cPBdCB-uE(b=E`L;2gC-MC)76atSTiZ@NhJ*#Pc5OVroc17^h-vOMKex_l_K#jE z#8!DF9r=q2@?|WbOy$*(8en*!e~3VkMwFDF*p2^c9>SVd7O%}iR{pQ%Ap}pDuEi&d zB(8iVsSGz-D;h#*VSvy}DoA086%?$en*SCFw3ox{gU!#ky%}$M@5Ttf3KCXAZ32PD z4~#z{I#(O4*f}TYZ06(3@$qgr@`RJ?n35N_=!hfP!&(wwz#U)BkQf`~krVtEqwAYn zd)p2u`#1K=$%}P>GAxkK{z@`BHNd}H*bG>EB>{U4i10`%HwiCI9d^utayw&8I7L6kT&&b(@}!=Vk3Rpto%;!xWg8Ew zlxVWZgRgftG&Nf0 z4Hpsie_5W9zbs4t@5V<_98J0sIsQM|9yST^5TQFdj8PGN$>vKYA%?yW!q+Nc6Pmv* z55W0Q(X08JBb@;p>BGlAE-VUe`e%-$&Eh}2On&2;TAT3>?ed`_u1Ajb*x2mY9k~0j zmKIB0LPJZ9!?iZBw_kWhitHHJgABj1?3Ij;bUnV&?f*9e#FfsG2xFwr7~fKjnpj`F z-p2x|9Itye7qz@7Wk!UVs4|XcgLo6G+y9TCJ7D!h`B?a$f43BC7P?V+AD*m_(v~Ub z-$A40{qjV$tmoDfFW|&`u#~>XfN4DQX7d0X4ZMo(T@dYBTy8Vi_!xc=ayA^W^WQM? z!6AtQr?JSY)Vn#;f=){FAf)SzI>M3OSeU&M@IZ9`s|TXr24ek#t}!LlR=K&^D!+I! zz9?@{liYTN_I;wGexvCzh1;cqUEiD;z%xd@TYx^i@}zPS|M?vdq1!c@cGLbVz)F2@ z;8DNidv9JoaS+n-%pDm>-ad|#y@v>yCJH*g$2e^1y59Qo%i}cfAvw$80KWv1MS&~) zXhrlF!^log|Gtu4p{G~lQ3f5~b=fyb90C1raphCE9n})E63m*t&ew#YiL3CwrFo&GLFdv=_qZIx?=vY~Oz2oWByFa{@5f`l1$HH_JDRkA(89#^ z(49L!=n_3}v{`N6e0Mzl?zUoeY&LUjU~IW%aBgk%vBFo^^m%@x$Le{$VC?ZdMz=-G z79lG_$GR$HfO3zO0ak>nO0$4fetu$p)NbyH&^1k%m8J?7Mu5MruF7o=?V~5;TLQUL z-`$sR+Yedhad^fjwjeA8Pae?TZ|B2(N~ko8*yZ=!eJl!~=7*2F(7PH^z?7TIaC(){ zK$--~IR;s6e)5;w4A=E@Ikk(D>|eRh-l%r0yW`IaX;s>^4RA3d!*&8j#JioHe98c$ z+h^4;40y9GcRx;yu*P;Jw5?il-O+zYF&&`n=-5yAHC^ZvcRoFHw!*TCwuI zr1BH+N!64j)dcvG(>doe#Low^#Yq8a`JV}@_a<=x+u|?x0xI&a>tMdU#=+yBs4Vo8 zj;=upnr`}wxUZZ%Hx=GD#~bmir3@TzD7<|oSHC&dG;h3vBD)bTnQ`fV#7K7-i+~tu z9hn?@(yr{DG-BaBokCneve--@Uz=n zm{ol9(n8A4Vn3d9ODw)B$WG^&-iYt+H%ES(fzK`}H#ZF_TL$w-;hR4wdNn;p3dpR` z)CdV;lYbCf>(06Mp}PI;I<*Grsi(VXj#H4;3riN~25rV1XJJ`arTFRTtG|bsL^DVR z4*~p53WyMPvyYB+aF>00%!|=b7;7PmHPkYv-W%x`&uH%1A!qr6vw(8SHUCGY&qkn-~(*>kh zMm&c^=z6YZKud7N0>@QSKoJJ$KOB|pHb1@FQU=y|6Vp3mDcOjko`3BxbK_t`>jSP6 z&s)Vlb&kHuB@1R-d>%d(T5g!g?wnr~-1kKILM|XbSf(0UVWslKrTG+NmK;L*zP9iK zJh)T!Zythe1ycuP^Hq_*hn27kcCd#!gt27R$6-!}?OJ>iSGEIII>?%&+|v}e-q z!`@4Ig1fIrwtHH!#w#T0q23`z<{!w)b4Hpd)YP)@xFqmI_JeBuHs*jGKDlzK>~zV) z6EdsPX`E~6jG3x-zA;k|M?D%=)^ohUg1`BvJqMGzo8JLaa4=w|rV{>_NoRggSd2R} zGnIQt;i?cGSo`;uZ91sPJj^IKge>o`4$AW>&j>27C)U)YS<_WvnVnU+Ny<4wHd18| zD8G-$wm_vqf%pA?ID5;WxYn&(7zq$OXcII*aCZ+O0>Pce-JRfW!8IXx@Zj$58Z@{z z?%ufbEwcAM?>V>5ee2fuhgn6_MfcOari?MiSS9IuXG(r0>4i$&`k>`lL4wIh5)le?NjVWK;o%N^NRDxA(!(~N~f*rr~+ey=S5xeS^=LmzLnu9F4 zvns(gsene+{ZY17WmDHR{NvR>BC3ts_RnizZr{WTR>g({xd;L!uws#en}tbKqN)a_ zv0tJtbpo<+4jS{rfaLbpe==uL$o~a&lNo&i-4xxXPhm1!kmQwj^o6;;hU&;GzNQ}_ zmB+e8{%idQCDTiA=&!D@iBoa6k~rix92wh8&~$~f`c-yFGw})MM_SvDvy0| z@hcd-zh-9q;Cq)OXb@U5vRNjD#&pu>NuP<4a3y*Yh3hT4q+KbCH}0Q4UeQOR{DjMY zOCNm$<-`dQp7;29li~Nb-3PtRWjmdoLDp)5d>u~jNnWItHpU!v*y)j#x zAzZ+@oSbWybR7S_Kcc`zz3iovg|IOLU4pgy9ht?73(8Ti%Dab{ zUd*`K_>bd15%FWlV;B>dP%AeI6OGhRT00aBo#@;LMSmX#vL&a1HEP=|Sp|@c#Y%)& z1>gsnF*@XRKCJg-kl4I?m?f^eY4Bz5QNE_Pn@uvFH``2u43L7c@=#0s3YY2YP)dN8 zm|DLpc!(4w5O5-HMm>yBO?^-h{W&R^Nv7L%)kY!NhJ z!EJc9z^6U5{1>Lyr1KNN)C?c{VM`fSl^d}2_>Y)_V~yoR_}pDSqU{H+OtRV>rGMgWkjsAo(>e%d96@O*Gc4vl62qw}eL3Dui3lPQ}5D4S<3*5BlRaR*V7jad@l zi4L{r0j;NKte)S0z;~V`tz<{b*VJDp+AfusOzyodU6s;gL8{R2TI{*q zP7FoAew3TiS1s{;1vk(TUunU%mh15Z>g<@u0mKN}fDBga(BR7DGxFLtmSfaAxYo9# z-sOKaK$B#E7A|0Nw?;|?W#A3`+ibG}rG_~}{?;-y4RQJGgZ+Hm@+vtxC6w5A*>XE# z`QL4LmxFeMy3`15)Q#dU3hN?xjHYluc(PMg4(^N{Yg~2DPK5wa0ch3bL2qd>55j^Ix8E;sIikuDtE4xN3Z3`tW}#=lFOGo z_lr~+Yx#-d`BQWyK1|{)Vz$H;0ty-m2Cl2;*)JHni8a#q5PvH-NN6?a&BSGAf0{l5 zY6)G3B2REhLBmj^WGAN-kUNHaOMtI}LoB&odq@2I?t1G4NLP#XqOE^WZ;);()oURH zL3JpTr-`3MkSok2N?*W$$0G=!Vx7;^mCa->1WX^DUQVq~nF^pVxb3H}qII`MlwaL{ zx7mu2;Z=(`*VaQ|_@Nhlaqu$&Gly-gADl8U;h@2x_}VH#XpEx9vGbFpw0_~Up9p65 zudD#f%xr_|AWcIhcD0~@>l{P7B_w{-*XnD$QVXGaT*0>AaoLc8QKcGEMK z_5tUOjh@mdIBu$r91pJ2t6NCfc#*4PypSbD1@6o#C-8@o-^7fS2KZ;$^-UBL4I;{wV=A?w12} z&g0?e1SXjI&vpKXTS_&i@H9=1I)hZou*zKQR*+1E;lpSNb7*BV2pheS2#%xHB4Zbu zw*OKMG!YB{8|+s?Cd;wJ@|Px0cM8ztsdA-Hjua{H(96BlFX5Wh&zJqL#Y6q6c(AKb z(d=DzMg3Rj17?vphF4(xbqR1`DCQVShNxTLf#)yM&;L)g16TOuznC8gLw;#YdA**q ziCgkMxoSVTs4(SEAr3|RW!P@1mcXQS&-k7aP*cAvU7X%$BNA!U? zOIV-wF@6(*O}obN#hMxZ$^q2I*8PruPG1|}_MS>JyAaiwpuj4bPDO3|D=@K+Hux|s zqtwBb`0VwLTOv;Xu`pS?r_cf{Om4v#E?QWnC$Y~tTuMbIaiJ2GLEx@7YXWU6`E^lv zfJ!4i21r_;X<^^#R?Rmr77&~%dM&dShbH}2r zNdFIcmKRqI;-Zr^Ru9}t(m<*j@_nirA7b^q{-bIDqz~EA5>Pe9|5Y{Q(v<$QYD6na zvYiCx*$NOu?m|3lojK5M?fRL`bf|1PUaN_uMid5wb>X6f+}*?5R~|Y28K;|g341>= z>NK-SZoHP{c(3}#{Kkkv$Dla@!r7^i_C^_0>c9|tRy%;t z3EAMb^WF2)Vae@KYVl^z6XwraVcoA^u;vkY5<0I~{&2oFc;>%@VR!%FG*Z2a*_Re3 zta@$PUS`9#U$Mdws_NhBpztb=tfc3$vIZ}JprB{zF`xWGUTB0^AO4k7%!@*+Q{Q0) zP9mVevwv#|q%M7<4@L9nVncJRxVOTOF=-g`MWfHf^fT=$fNjPoO<(Sc^DTucd?rAh)qWg+Zo6n+^$Av0{IHC6Lr~~N zJVj90`?mf(nUX8}ZtOMPw zs3`UA3n`CTjU!v3kEL^zWO+7ftt?)Nzie+^gAUYx&!F!@R?f|KN_$j~@}~{2jtRoa zt{44v`OzR&mq;W_;AGJh%~{dH$*S15m-h{s=7D2n9*^FS59gF4&A3U%kCUjPog&S* z;Io&Ht@Dk=X|naF=X)$<(7pYcmQ4ZYJ3lwUbCj4OsjpZDgjiwc=^SgD&Rj1YKlJ=~7k ztBnt0p4E%paoys3eXGPIkWA*6vdaMLRw{b5olcfjOB}GZxg*V~!8P{t((z*36T!`f zpc&a4^lmZQ8RH~U(>6KGB zRl_=Ft+H@wU_WO}a^cR@v7*=Z*?=W}XTCTSq>Vy?*Tao|#g^COJ&AThGoAnhX=G<9 zvfD=gQ<{HzF#c9J#`VDD&am*>{0oBk%LdFUIL4*A|3P0&ZD-u&t9R?ULgtp%8 zvga=I-!P8vDyF29NS6=PH(^R&y)x>Au}Qqs%7Z(G@xbtj(sYLrgh@kO#@x-)UUPG^ zd590mI=koH8c)QPEkC@>^)T|eo@6C6>Vn&}`q(n-lB{z&A%S(e#2)18H+v=B(bQZ_ zz@JTMyykliz%QG=uKAtn=Du~R9^N(%1CGu-9+jxVjDp!Gx5u~=oa1uRne!v}C%xT; zuTxAuIV0@!^vrzRQJ4jcm;&?7IGa17qb!B($FdV@78!R=3X{Cu-lO1ra(1I9*ZKrD z-)Z@P28RFY$p54^GrXAc7TE*zW^jCwy^$TERzL%zrE;bk!{=V4s@IEm(RV^BVEClD zjD4x7_V7KHVPAyN?WffuX+2++SILs*=;pW)bpPVp5(W`^vSOGdq=; zJOOJkgvyQ-*G?Ow*OGB~AcL3X&V!_?d(Euq%_h>C@{BuT-%mSv-*bj? zFIZf*Da9RC8#?|BI%+l(u~`El>N1}2^AB8ju|DUzo)|^I=c;?ZS=IvZ-um;BkDTMp z*rOd9cHTWdKsU_be=49BC2((-Ki*BsCVtD zuf!krb{3?P*AFHe@d}7|)_;BBTeq~hzIdWUd^0)lT&@&xcUz7WzM6P)bxUOEtLybitkq7HUGIB34Ti7GrQKIlmI=HC{o5o$Sk*S8qE|iAX?|a=PEw##6 z&$VwuZ>E}+=VR6bv2(C>u4560z?s)@DEMY2;$rr(ASiaXqLziQIw0z? z_MzH2xIF9FIYWf2fO6 z_Aa@OI0SE&GsQGa#r5F257eC9#T~AzEi$oc$Panf5b0l9Ny?`lU0b&C`r~MTzuKDZ z^8)JX{?`(7`k;fna2+qlUyfkcGB4t^O7R!f2BD_A<^l4Xv8we$xSJ^zp}}MLqdNxB zpz4oGJWGcmrj9A~C;|nn(jQE~i8Dt^f~7Qbh2cj>TEVw_`g2W;q|RWh(RoTcO=&E2 z?Izvx(9?T9`WBWNo$6WBH@ch9)rT`f4|-)=iJN#NxqCMy7P4W=JQYXDz}U3$EG|Io zR-E)Dslg~Wer;WHy6;CmOH10FW1ai1((SZ*DF<_``Bc_=&I8|eOPE(K=7UB}Sn0k? zFQxXcUqSafxb23U4qKy)>*yT=!L7vB2Vrf z#9N|>XBa$eQfY4@%@1E-;15kPUXiv09j)Bfw?uLgQV6bCt3OcfhsccDTUgInIL>9Y z8xmiV-lT{`vwpm9B$<|!&_8aK+VUp%yfl2>%1;8by$9Ri(G&uwR341RwU8TR5rQog zXRb=P@t935XJu$4n=bo;_8E%ryL2GjAR(xi2$wm%(@F%k=vCazBe1EuaVOJrU!VPC zpJ0j_8=uP8{~3QFLo}P1wPi$l_Dl$zojZK1KL8^nBK!MEjOU-)nz;o_CT*V8^K<;M zSUAQ>8#5zG#zD%*I`dtYfs9lJy9WZ9d|tks|Klh53`TpD;)Gksb*#nG_nU}yl>0Aw z#TUpc6zKv5<;7-!=IO?-R7!->BnWU3W82e9KXJ*ekq9m+?N+~A0#ojNALehb(>#I> z?{mKpe?44CBT)WP(zy94HVy;xIClO#@kkAgT7K1HFsl~YXPRp;G*tpGa=0+1FL&wLb1Jk5-$8DX5}@Swm#Q?yfOB0P$Lk=^RZ5Mr*aSrx+Q2ZdVUy@sWB~GFhiGl%j&+}vv6UY zp|Mp~h5K3xu>pYpw-Fn7a3R_A8s+1ja*Y@b5VL&|F|-}~hvdMLlz_X9U5A(ohx>jn zJL6KP%W6Nq3FoM_E6Q9_Q*GoS)XsB?BuDZ7i=m8JrR%FWZTdC4H41yv7GjtoqA$kG zuLo*4(T#3>Hi2rH2e z5R7X!f^w9S8aZ7+`|gmmN`GoYGvuu!R=sHJiRJzf(u4O?r%dRz=?m^&};OBWPZWRzB4{RzVK z@DfN{gTI7JQ&w*HjcIp3laorCrg1(9y}p6*Km^oN$1j2KNSs!C8`UD*XsYwPSTqkt zWa}sH+4?zr){_6|hd^RSdC@|`AdOJ0iQ>Y2Ho^gXp77BRI9%)PrmXx(qE8T`y0vJl zQ3f%U01N7pL#Am>m{IVNtCR2Qq#QBH6VXR*q>bC%#mQ+75JAS9avovz8xbPqHCex{ zx>cmi4Xj_%rlv{&4Bz4lT(Nf?;a`5EQLcWe<-gG1UkE|ourb(@m!j1PMTY2$ORZ~% zAwvqwIg-+Jt$S$_T6GQ%s|jmOnr#eA-R-+nRvz(t@0?9u9rA}%#pQ)^c5scoavalb`DCue zuP`6_4v(w*%;iAu;!}jHaWvsi#C#F+=o{G}i|A)6?5jGXw-`NYaKu@d*n zF=~Ai(5RM@tONi*gH5>wvFE+i88b7~Ng4Bm?C;^bf&D>T_--|^@>DQdVBMy(P~%%D z3T-5MZy0S29wXh^`*;H*-JQ`F?|dFyP#fzX zVUOWbE1J7V{o-zF zYyDUH;_{DU&r%vUJ)V6?0A$(Kg7{bK!k$q5-JI}i*g7Pm-%iL@Yw*KxR{8WAsiyv_ zDDvW=t^(GB`=j`hxFQH6G;78|PaKih3d?zu#3^XPjzmQy)U*O znMr1MHyT(OoV6fqNYQuwPQ>88-~CRc4E*Mzpea`#&>rz1T>#rcWtZ?X46DT2B1=Y% zzOA#%HimMcP;kRX_pVJbqa1?Ywgx6qt*TW>4}Pu7ELW&N_;m-ldXBVw*$icXtnwAm z)>zNdt;QQ+v|=d?yhxLYEX1pu+(7745pG0D=&u^hxI&v0V6i2}gLDAb4kwmP#;1jNzW|8Oyc? zUj=w&=xl>-fUxxOK5h?FU%M7C45r=tI#H+Xr}wea0cRQV;y7u{H=ee@I;XyiDNX2@ zq6f5$-~y}d^Aut_h)+^rTc3CEC4TTDUL9(ETY7nrK(hH*pw-g!0{Qq}cy)3Y?IG$2 zGYvjaWI5th85Nl?x>l_MV~pLJQDj?G@EL;d1|@5?0}KJoFl-wzHo`n`z%W<7uZ=68 zFpgCr9iU+OafQ%h;rCR#FKOw>VZyHtE;H#yx4F;_P z?g9;uZShKvZtaV8VDGsPbQ0h}{uCnmc__^WeIRG$3_8W!UP>NSvRI9`z#gH#+rRb* zb$LkVqJQBUFWcPQ#g-Zpc`GKqmVO{LjBMrJbmM4Wn)6&^Zf{kpG^sY6I>9yU6mte=i(Z& zZLl0ADetU)?x2*{(h9%Qr~j}8i87|kk;aR-{21Efyw!YI_a<23^AuZSR=bb0xXF0u z$-RONEwhha2|ij7Tvdzr%L~8H&BA6x6Rz_aE(iv}GL~94V^`DK4+-PZJ_l(m=zY!M z3+2AHu#hAQEflU!?kggp?hS|9^Zg7W`{6%n;7>NVXWri2I$jxd-Jd?vCg$aER-o)d z|J)k5P~gBebVtwbD9}CWQ45}>asEV_UyNjvv6+Z+O~`}Vq^Ou zxLl)Q+g!0cKmX^iL($Ed=JUr|=S%4jO1)N}*3glBesAd&hq08}{5k$y&x~;it>(zMx|zkhX1*MTZ64l=VxyN4?F0Y78VK5`jce=_ z^|URKW1Fd!HYwt9u+|Z6#hQY(2CAt;uknAjG)`eG2*Xm*}ql;CZagl6nTkxgo5u9w#-(-?` zmVd`OL+}*Q`3(XBXbc>`JMrq6A90xP}lv8ZC9>YtsatZ`wGs4C4! z@pPp>CAv>Zq1de3E{MJebCRJ?GtoWy+)atkZsF7|A@ z^kBnIE@_E`!8RR9fg5A2E7X5hCHz`YcM3IDIbOJsJX0yz53pz;wR;`cY&v^`Mew!t zkJ9PS=`=;Rxlz4=_nC%W-uYCB=96z!H~9Uzu9z?kCCb0f2GM5=Mzylbr0j@)zfg09 zhsUeYl^xtxe=`fpX#K~qjAdV-w?&=$jla({x$eepOZ}i-ck>5W_c`Plv1d=tkAE4` z!j52-F^7!!EIOux>(y;ay3xj?)%GIxg0#Et;F{mE{6;+k!jWGzk`PXsZ@Yyqt76H1 zvIP!3BjeY0wF3fTpLTwZ8OMA^#A&qxXrwG+P<^IypK`Yj?bmj{8blmWDKcTjG4Q$RlwaqCNcyqJfF4R$YwxZZGL&`W%AILJouKyP&jsnJg4i#LR9G zz3k+P^(uo4BuL5jHyN5-yuij{FAvzW+J5;~vSe;Fmigj5xt%W=rk$hjFESK&gamB+ zw_OFm$wCi83Boy8o4U6<0kHL;uL4|XIeoWG=ykIdE%3l756 ziY{}R`Wu!(McQmq4t^HpkybFY9|81j*JwUQ#(0fs}G&;vZ?qh1)!Cy zvw7DMf=hy{3C0Ae+zt04*@;|BE zbl#4xv*8UicRegx`;Y|;OqSD5o$LDTdzf_z3$28!18SLwp32uX%7uqp!R?UD$S$f# ztNYMo%^dNCXCbjv06U#D!4VA)PwizXkw8E_^}8#3&1Ph&Rp?Evj+qM5s-uOocSGm) z#Y(7lBN*Jd?cIgr$=1>&mnuu#(aq9ihUu)ap_ir8jFlTBdX@a$>X~dGTKLI1$X(?IHPcJk0Oq7axVo!6ocmJ$HlvTsbSn+)mD%&}lv1}6dAYx_v z{`FvwQZ;`m;_qhShL}!I6i7j=wcVFGtKJyZhHS-A42Uz>-MjU}B^BQSY{TRSo_o+NsDP@5d*)WRDNJ_Vkfd$QsFtCukgu#8@;OOj(w(dcly;MlYdPU4eqgcfpZzv0QoN{( zZa%~;&QIfwafyldgiC=$Y>Fs9N>Fomi@e=C^37ZnB=1t9syfXq$o^7&JS`?J_3E$s zO6L&u7mG@xp$F85}qYPN@^wHgBHu2C_} z`(!5zfhAK>;aY(a|3~}8O#~!Mb8g5I7m{wnGbzRQtBg`s8@lx2hI8zCZg$0FQsr&t zmAd9HbBvPv%wZB_lCXiTEFQCyDpu30*xd+T+QR~xea;lteJL1v{;mU$T5|sUO@G#f zyPKX;UZ-od$t@bNvA&~MvTwO0o0Bn2icwLP3`q@5PiRXT7RA@{uU_HZesrw(fH1IK zmDOozCw-MqVYF&B;b8bZ0jX2wlv6cgH!4`mx>Ut;PXxM^Y=LV_bmv^ zW7Ty^DW8pGnX8{y6U$~wTHMs*qULfBWOJ8mZc=#TY4V#%)ZsEiUc{#cL%&_Rt$yh^ z))DU8JdH~muoY(IU>rGl$tGC$a)jeRkb`&=;Z7K8c_8wm=IoQho>%3Yx`Hmn@=55U zm9tHwS?lm@?Vx#~_-etc*#3f+qt7jS-Wv;Bmz~psq^0vWZr>X3=Pef@FVBD37nm4} zmtxp9_QqlDH51y%xi1N?Y&@R<97++4Q=OFAws zf`qR^`WdDCO#(iH^!O~v0DLuEe2ZoI$g0BA-|rB7>Honv$r4}z!m1$JREScK+-T3X z-NDyPs=HWLz|&HX1T2%#ywMK%rR~`HM0~mno5q+^Dvx+LnI70~eMF=;XEJ|j9hI}( z^Oii+Tj@@26eST;DXJ)!Ecn!wY3hsrc;9@NnWhZwelS*>bQF?c8qDY@h{^e)tf!n^ zd?Vq!28Qf?z{WT#-(CeMk-0FHoI$8pu@0Ve&B=0xK`t$^7S z^GvJM#6E8gS8q`E-Q|(;%kz#@6Sn_cSJ8ORaPlI@}SoUOHR` z@TO0QNEeq@2rYNjdppd;2JYwGPyz-~Ir7&}5D8o;aLjT=+@X^4%KLrQg{!nNInuZK z^X!q$tFuH8&3!CkKt92-h1T^rquiM%9GsGLu!PRSF43LrMq^$%4`*fC9vvdEOvDs+ zmpGlQpWO8hDKFmM_H*8VOs1^P+X+RD3Y{IN*FZlWmz(-Xf`lHUN3II31v;+;pD*#P zOg8Z9K(9O!?vsz|?g|Lfa(R#3F5!mzrH86*X@9?An^g zHwR5JnERDY7r~#KobpYQl_*L*?_DCUo*xr7A(pmV9N&<*3Sp>w9{$MbqDH2FJCjVe z58XpGE)AI7Wr{jYIN-zHlh)ikGvIzFz#TD*ksXHstSM;dIsuJFB~SQl6vL9AeA4`m zmSirMKq$_)dfogQMrpKQx;Xk;{$Yx^9rnb^R$nJZ5{f$gN>%gc=}oF9$Z>wzx^$`y zW{g~EEOA@QC~>wO8tCf9Cdtr=!)sQ_Rl4O}mjXu~E`Y-nXco$)QaGnktlv=1`P_Qc zqc~J6cywYx`ly}yqhP2eHmv|5D1P;L@U38{UKDSE-HcP086WmQzE~ zyl5Eyokx8u9*wRfHkIS_H8|=pp-Y!ku#wY#24Fto#>n?tiL-RWRh>{gvnqY!cz&Lu zNrJ;vQAC8u$*yuL!P*0M{RVuB0N*unMY!HJXyCHT&K+`~>~r)tv;y7Tniz#!3wRnJ zS}%ssE%aV-T#k7Knv&`3o*C{C4n<(o_oUwIqW~@#KHbG$&N300$k@zby5B;7()sGA zUuntTjf3DtSg;{`UXAKz*0EV$!YV6iyx^9Qq#@QpmDTqLJ8Pc{<#7x;3>!{(53>}e zv^@Gws8~NA?7qU+m({k`%SVULYJueKjItRiIr@2|g-dAZw+gN(NARc5y*wurg}lyk znWR)}ut! zfuI~`sh2Rq!@xq!rF<}W5v$9vIFn;F6DS7U>Vbzz(=jen2zi=nu#zgSnc(iXY+daO z<}au#@9Kb1$T++DX?~B>M#oz7P2aMPKfmwVi*Ffm?>Pcjwm1aKn$hf#*(!~76Kbx> zfSx&Lv~U=^lFp4}lPz%@n0%X6^%0*M>8F0WK$08!{a}yJwE^qxw+rcSCJ z>-fp*-3vKk2*qnqLx6RL2Jjg03}}pVT4T``r!Gq^v(^TWBT;TENy7%mZnQboi~t!_ zp!GK)nwytrgSS;TsgJ&=T#@Eywh`NpBd<9OFFy(6QW}DQ?S0_8LqMwn{QK?NWV?3% zsxTAhPgzi`;?dHdydwhwYe`u3qfv!(o@NuwPUgPe<+m?pFKlf-%axfv>NN?g-<`IT zU4T}#Xx0?3Q5lg29{WBQ5D!TAUIY);?`AqNmXdE10KabkTt>_YetMJRR+#*=LCxDM z^P`8H39D*bs27wt$N1?CFB_!zwiS});>EvMiwkd4tP}i3aDdf%3;GSb)u`VQ@xx54 z*7_iqMubI)&Ejlgo5UT3k?(!;6<$pr2cbF8DV(Esz_~#Dtbz3EP}pc)iCHhoQ$S6? zD)2S=4cshcjV^>o<8p&GIkV(57etV{%%bn-VY%1D$Jfvtc@Rf^8FLZpo|nX44>|I6 z2b9bY|C?Bm9IC(IBD?)zs&!rfRGjPXHc@8-rOh>NBScSuB1pDs?N~VB8bW|Eza(X+ z7AA_@$pB;t8{f_l3rylgG>pHSOq|ED0wZKbM&11ms?XK@WKig8wPoQx=>>eMXWg5| zmL=f2es?+ZuEGvAa@PN{3wTx>zZd z=hKMXESr*~fF=91JyT-!H!{upyzoQ=xtB_au_8ut*YJ77fk+2?c?*^!zZ&A^Di!Md zi7ot#)^Q58*;%H|KQ>{WIKV^zl&bd~Ft)u?ac5Vl`C<)Giwg|$<9$<5sssKWxL^MkxiKo zi88ES#){f6#OMCJ30gbr|9U(jn7*r?SD=oqVfWItWZ-pR+ne>XEc$o4BFb`jN(jm# z$^m(9j5qDmpSkSzB@DA`x@jv(vfshHoi279SFWzA$}yT5g!8uJvtz?-_g)NeZ7^I*m)!ljdsUTT96p&+B! zT_{EI$~sgrg)@8ayPn9@*i8z@AvM(EWkGRoX6E#<<#Y__k^hA0;JC37^JyFRuch3h z$R%gZNTUKykiaE83@5#OR%me#HsLujIN?*KD9Jsi8vWmVC`WzjGV-w?1Ia+_@-+xr#p0z&GIFjYsZrTJ&DvuSxxpSXrjD4QU<=qmz;YBh^=ks61uj z)HVe5MO#;KM*@!ldUOr4iWeKA#nfs`3vXEulBF7Td8IR_s6Tw2Rkdpf+`g{W+I-2J z75QOe5%2uH73eP?uqQjPNiGe7&C{WN)KAh{0+pUrbD_>PW<)hB@&uqBhy)S%YsfsP z2MuI@QHoC&>R z1EN5huhqRb<@kDa$vx`yh~gkX_FCIE2h6NfyMuH0PFgS0UFU)WPXnIHhdzN1L^c%C z#hTnvl11$F@|+lGyCFFDWnssff1JVqs28kP8L(nq4WZ}p8j`m*A2DyL1`NTR7!EYR zd`s#dev}RVTHv_%_;;lVu~U>W4U9Z8|vx63?dZrn<-5`NQt3+@j7_vI$%oo z_vzmFJ<7BxOjxX5c`j*~)x2Zg2mCaP-c^Awhko6EtHV)}nb7W?>}!=Ra*YHV-xEeg znMQa~b-U^>h8%tVUuwsr6LcghO<29D^~DkY%e2t`?r|7wi4L z=q+o+mW|0SGJ|Y@jD9$g$1$AZ+=Zex(YMCLv30l;$Y>4&U5AtKn^D5(Dp_e@j(NTs z>h((W%Sn!Jf_!okAdYQ9ctKMlF|q zkTV;Fr!x}Q37AO$Dp?5YA^sCCfl`;f@t$0SU{JaNnia zt$g8bkbTOZ+?;B<%!sSjK)jK<1G`V+8VpI66omi!>ZuK4ese z4S);euNsLBW8RyhR21O3Reo7Z=ryF4x7a~a>)MB_@L_y~+6Py0gg(nEXsRQ&VeBsB zR5g(rORZxinLRsQwN5Udh^<`AogAIF))4;b`?m0nIBt7IH0_^zW=3z{N-EtQweYs< ze$O#=n`W;p(pj6A+jyp8YddGmQ4d1=4$a?H;g8Q)2dLUQ`dQ~E4n`vJ~(=hG@u9#)blPM+D=Q+>YIFH3?m3b$&P1(856O+ZAfb7E#htEr&?Rcr~ z!F5Zf^KudA$`z@H_+P;pekVMUTVOwX&!&&TjoMwa#TVG^qo3}AN-Lx3{ubIRvw+`l zZzt!IK41H2ah>VA3+acbw2X$ktwIgvk&C*ca#rWP7Fs+V#t&=Vh1X^SXw1_INhFVS9bby2`v7rs z&r);o_>y7tu}Xqb0PXRyb;Jbff@S5b{RqBmZPd=W zz1nN>yl>d9Z<$&}yd&gsYrS-=kbaR)5^*EX;I-53TYO{hHoar=j7!$$y8gn&QdU!G zJmXDEjgG4>G>RT?-z&T|vN@3VyW=~WGltdG?MuSe%y7M4@89l&SKbvoxNqW_ z&g(5jzvPb?6-=&`1yhyu#FM=SKWG}8U)JxxGCk-{8<0pLnleXoZtPwWzS7Lu++wGG zu+m`8NVQtFfB7cfYQsRTudiNx+q*p*2_u{UD<)zG?N7toG-08w` z*QGn`$U_4!M<7@rx7DSsUI+IwNT-E3E&M{@a(4A_(U$dcK6rxZHtguxT|olLNeX!@ zV`%ScTxsuR-&6*b`xbf~$37RXe(62a1B~&!{z6V1RaCPUSok7!u;LN~|Bv@14QBh| zdHE#?2jM(@clb!Qups?-*6u_Ik^6UrajrZ)i9yooq1~-fjsH+7Q`6)V@ zw+gLH*b~pk@Tu+Y1u4S)<*@eDh#kdic8Hp<)AHQ*N%C=wrjE%-k3-3@yH*V%qf z{+tzW_+J7XzXCF`NlZzr{e-v`g7m%I%tC&I_0()3bT$}3Qst4p-0iD2a%N94kxP<=ZTptDQ%cWM*@xSxuX23BF=M>7Z_1vB25Eg|I1h=2ED_$iCLd;;N;ioRTai^cce4jaD$$4_ zwQIr(LV2$nTKf1e1*c>C***S;m)NMy#T2%E3Ym=g{ITq*hGvP81RSsXD~eJWqmHeF z43X8|#YIyY*1u7+z8j-vRm3wU++`cDl$O*NsR71?!4DNQtT=;zyxtYr>$>1Oe^_5l zpQtT5RH{%P&1nZB=*!u{CmWswejRX&w7G6M)gg$>ysi4EW36PAJ?2IP_el#?VUjNT z;)~LJ4Ka9Ab34QfkVDe^BZn;T8R}q7vr*CZG25L;JSg;hDJwY6ZP}T{x|hpcsIPVw zphhCWl&^Lsd(Oc?@m^0Q0na!5%F5YF#D=3yQqL91kCcn8rliN$QEBI61hfx#u}7}B#3K<3^tEN)>D0H@vGNmPC(uCt4En( z{HJxv764ior@vblxW$*##r+Xpnv;i;Sx)7OsCQwIpuU_qAc(UUzq+ ztM>se({3wz!@D<7yz;W-ZZO!gk^r#ZvKA=?23$8!%;tx)Zr&9v#9}j8UXbJxMyIDr}p&p+IR!uXHH4VY>WCj3{+~q z3_S{P2R>AptLy*RqyAkrT>MAf0Wc~sSKzn(^k_w+>Bj|SO3x1ZNg_)yVypGCg^ z6sV3dL=c!Za{TtkzaS(!HWS#HLM->#u)OL#11x`WLmM^W3yS#mobqY=ol_IOKgPlB z+V_7=1vUT2RG_}mVawEmCK2*siqxWaPVvji-`2r?)xb-5uyPdfG1lbJYq159q)S}D z>w+ow)%*3PDQdQUbsdIy&3trWE<@diYiwgKD=eGsu&tf5jwNc{q~)nL;aZ$KX7Xr^ zy*QXa-MyO9)1Qgkp8s!yfahUlDu-Y5!VS^1u`{O2&gwN)i`%xC9pwYrn@jvI=l8W9 zq9h8%&)l~ty|zH={vso#vX_rs)4zZj;O<*@_eXI8O|KX+s%q1C1)`vCb z2UXz!uGINAt|U7s0M%mVxv^7jWQmyTmSH+(aU!FbCq0zLK*2u{4!>Mtez=C7iiKVN zgDe3KQIBZ$V7>%_t_#gXC;@1${SNKl9E3ZJ-5Fc(|Ifi7)ve7@%KTFi#X{o*X5Dk% zMb>2nSh-F(Ti69A7#_O=*t$7*p4&2zkjDTh(m7XDS{WomR{jp3>%?VXu-?6aJZz2z z!R|9{DnhyRcldHit%2{ze+>xifNRbl^PHlxS?pJDZs3EWw1e~p@BJDE{>%lWdV^I| z3YQW9$oKy`7Hk0s@1V7^z+oz7oO5N}faPde3F@I=qsFo|0FpZ|`8Rr3Bnkuf6p06J zUS7RJsSMluhOuvvHIV448-SFPp{2Pyaz!%rX8XSwc+HVVaNFN*g*gWsm z7;|yxcRx_nK*ZwshdE|*hF#PomwW*%uQSUv;f9(7uHlBpG7y^J%vUqMrwF~B%eW$h zwi5a4-x9IaSW&yl^}>Yut}>+qi^{Ow^qF*0mSz&Y}BGLup=%IXT)-C>B?!SX~aqCza-G}@xJszm_Os3HLjMj>zt zK&gsu;PXY5BY(|*Zy@|9mQov6<;7|V;x zh?GaZ6%`z%;f>rQ#E&(Z@K6!Ra=J8$NNNm{kL99=a3@;HZKvKSRRiATIjke05^gA{A~)r$b*!V6iJXU`O&khvoA8)QEl<@1Oh6e_=7Y zQFy}qu*1jNqyWqh@f^VX=(%E$ElW_7rT8r8R(CUUK3L*cbsA&B7A6Nu@7Aj-Ay~fQ zrYb|Wqz{#bwy`e$*r>*ZL%`0Wl(<9Y{`iVp6luv_h67l3S|MNoxcJ`YB<#}nPFz{QJ0rn#Ik?jlOVX-l>to=T=XB(VzARIG?+H2%oqDjz+w=4wta~ zV_n?je+PDcHo@7)>Xm#pDkSdwIT8130p~s;?BDESVUwSdchC8!@v#&LIeMd5m1yg? zRYBOOLZyw*S!Ceq+p#>Y{Ry~7{Y&zSgO$bgbluU1PM61n)ig3;?@nLw$L?5JxrN))$e+1aOtNU-@;c?%g)?rTChmoZ#Qgq0C@VhotQ9xj@8`< z;j7Fqeob6^EoX~TU3{j=<9&Z3zBFg9-S_CmRJD+mvf))pdcm1-UsH=4*JUqUZ6&#V z-iud;w>PUS>)>7Y1$cH$671}l@YWZB~*@K0Xyc`uE{N`o8Nh`}*j0z!Ur&8|Qy` zBYA&kZ+PCtitK%TQzNW4o0~pZc4M{Qy|tRvvd=twYxZ95-`i0Gzi`67Vek7LLf$Jo z8h3tacTcSfnd=heFK4;s;))pl*21uoX&Ml zyS1vX&cf9~8-AdA-^W=z57(^ElnW@;4Jp04c-7;`ZD$Iu)o)px#f)^Yl~c&y14|g! zrAC0e_{(KtH5a!%^;nV}ZoO-pN4Ea5?3Klbqk13eJzOmHVwFnl%D-nj^s|+Axe5a_ z&UT0Ez|rhos{**Lp^S~iN|3Vp#apJX@_y&Ktnj_RzV-h;WglLDecR{ecZZ!{hRt}o zFZyMP|GKQN^XKkg+hu6*di&XT_s{;fToHEpN9@Pu5p)Ak^a9Ob&ZU> zan0OV { + await esArchiver.load('empty_kibana'); + await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true }); + await queryBar.setQuery(''); + await queryBar.submitQuery(); + const expectedAlertData = [ + '1 library', + '157 file', + '520 registry', + '3 file', + '5 library', + '5 library', + ]; + await pageObjects.hosts.navigateToEventsPanel(); + await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.alerts'); + await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); + await browser.setWindowSize(2100, 1500); + for (let i = 0; i < 2; i++) { + await (await testSubjects.find('resolver:graph-controls:east-button')).click(); + } + await pageObjects.hosts.runNodeEvents(expectedAlertData); + }); }); }); } diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index 9ee9e061edf4c..9499c235a5f0d 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -30,7 +30,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ['securitySolutionManagement']: { pathname: '/app/security/administration', }, - ...xpackFunctionalConfig.get('apps'), ['security']: { pathname: '/app/security', }, diff --git a/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts b/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts index 988fadbbdfe33..c76a5a7c22f60 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts @@ -121,6 +121,8 @@ export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrPro for (let i = 0; i < NodeSubmenuItems.length; i++) { await (await testSubjects.findAll('resolver:map:node-submenu-item'))[i].click(); const Events = await testSubjects.findAll('resolver:map:node-submenu-item'); + // this sleep is for the AMP enabled run + await pageObjects.common.sleep(300); const EventName = await Events[i]._webElement.getText(); const LinkText = await testSubjects.find('resolver:breadcrumbs:last'); const linkText = await LinkText._webElement.getText(); From 7a67cffe37a7d922b8d74024c150a902c1a28539 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Mon, 9 Nov 2020 17:11:21 -0600 Subject: [PATCH 36/87] Index patterns api - load field list on server (#82629) The server side index patterns api can now load the field list. Previously it would error if a field list refresh was required. The regular and rollup index pattern field list loading methods were merged. Rollup index patterns require additional functionality over regular index patterns when it comes to loading the field list, but this won't be necessary once rollups v2 is released. --- ...atternsservice.getfieldsforindexpattern.md | 2 +- ...dexpatternsservice.getfieldsforwildcard.md | 2 +- ...lugins-data-public.indexpatternsservice.md | 4 +- ...-server.getcapabilitiesforrollupindices.md | 28 ++++ ...dexpatternsfetcher.getfieldsforwildcard.md | 4 +- ...-data-server.indexpatternsservice.start.md | 4 +- .../kibana-plugin-plugins-data-server.md | 2 + ...data-server.mergecapabilitieswithfields.md | 15 ++ ...plugin-plugins-data-server.plugin.start.md | 4 +- .../index_patterns/index_patterns.ts | 19 +-- .../data/common/index_patterns/types.ts | 13 +- .../index_patterns_api_client.test.ts | 17 +-- .../index_patterns_api_client.ts | 37 ++--- src/plugins/data/public/public.api.md | 4 +- src/plugins/data/server/index.ts | 2 + .../server/index_patterns/fetcher/index.ts | 6 +- .../fetcher/index_patterns_fetcher.ts | 50 +++++- .../fetcher/lib/__tests__/fixtures/index.js | 20 +++ .../fetcher}/lib/__tests__/fixtures/jobs.js | 19 ++- .../lib/__tests__/jobs_compatibility.js | 20 ++- .../index_patterns/fetcher/lib/index.ts | 2 + .../fetcher}/lib/jobs_compatibility.ts | 19 ++- .../fetcher/lib/map_capabilities.ts | 37 +++++ .../lib/merge_capabilities_with_fields.ts | 26 +++- .../data/server/index_patterns/index.ts | 8 +- .../index_patterns_api_client.ts | 27 +++- .../index_patterns/index_patterns_service.ts | 19 ++- .../data/server/index_patterns/routes.ts | 6 +- .../server/search/aggs/aggs_service.test.ts | 20 ++- .../data/server/search/aggs/aggs_service.ts | 16 +- src/plugins/data/server/search/aggs/types.ts | 7 +- .../data/server/search/search_service.ts | 9 +- src/plugins/data/server/server.api.md | 61 +++++--- .../server/lib/get_fields.ts | 6 +- .../plugins/data_search/server/plugin.ts | 6 +- .../plugins/index_patterns/server/plugin.ts | 49 ++++-- .../test_suites/data_plugin/index_patterns.ts | 25 ++- .../rollup_index_pattern_creation_config.js | 4 +- .../server/lib/__tests__/fixtures/index.js | 7 - .../rollup/server/lib/map_capabilities.ts | 24 --- .../rollup_search_strategy.ts | 7 +- x-pack/plugins/rollup/server/plugin.ts | 4 +- .../server/routes/api/index_patterns/index.ts | 12 -- .../register_fields_for_wildcard_route.ts | 142 ------------------ x-pack/plugins/rollup/server/routes/index.ts | 2 - x-pack/plugins/rollup/server/types.ts | 4 +- .../apis/management/rollup/constants.js | 2 +- .../rollup/index_patterns_extensions.js | 55 +------ 48 files changed, 484 insertions(+), 394 deletions(-) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md create mode 100644 src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/index.js rename {x-pack/plugins/rollup/server => src/plugins/data/server/index_patterns/fetcher}/lib/__tests__/fixtures/jobs.js (65%) rename {x-pack/plugins/rollup/server => src/plugins/data/server/index_patterns/fetcher}/lib/__tests__/jobs_compatibility.js (81%) rename {x-pack/plugins/rollup/server => src/plugins/data/server/index_patterns/fetcher}/lib/jobs_compatibility.ts (79%) create mode 100644 src/plugins/data/server/index_patterns/fetcher/lib/map_capabilities.ts rename {x-pack/plugins/rollup/server => src/plugins/data/server/index_patterns/fetcher}/lib/merge_capabilities_with_fields.ts (70%) delete mode 100644 x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js delete mode 100644 x-pack/plugins/rollup/server/lib/map_capabilities.ts delete mode 100644 x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts delete mode 100644 x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md index c06c3c6f68492..f288573cd7abb 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md @@ -9,5 +9,5 @@ Get field list by providing an index patttern (or spec) Signature: ```typescript -getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise; +getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md index aec84866b9e58..32bf6fc13b02c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md @@ -9,5 +9,5 @@ Get field list by providing { pattern } Signature: ```typescript -getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; +getFieldsForWildcard: (options: GetFieldsOptions) => Promise; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md index 34df8656e9175..57bb98de09ebd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md @@ -26,8 +26,8 @@ export declare class IndexPatternsService | [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) | | (id: string) => Promise<IndexPattern> | Get an index pattern by id. Cache optimized | | [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) | | () => Promise<SavedObject<IndexPatternSavedObjectAttrs>[] | null | undefined> | | | [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) | | () => Promise<IndexPattern | null> | Get default index pattern | -| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise<any> | Get field list by providing an index patttern (or spec) | -| [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) | | (options?: GetFieldsOptions) => Promise<any> | Get field list by providing { pattern } | +| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise<any> | Get field list by providing an index patttern (or spec) | +| [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) | | (options: GetFieldsOptions) => Promise<any> | Get field list by providing { pattern } | | [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern ids | | [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) | | (refresh?: boolean) => Promise<Array<{
id: string;
title: string;
}>> | Get list of index pattern ids with titles | | [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern titles | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md new file mode 100644 index 0000000000000..ba2efcc9b75ca --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [getCapabilitiesForRollupIndices](./kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md) + +## getCapabilitiesForRollupIndices() function + +Signature: + +```typescript +export declare function getCapabilitiesForRollupIndices(indices: { + [key: string]: any; +}): { + [key: string]: any; +}; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indices | {
[key: string]: any;
} | | + +Returns: + +`{ + [key: string]: any; +}` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md index addd29916d81d..f0989097a727d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md @@ -15,6 +15,8 @@ getFieldsForWildcard(options: { fieldCapsOptions?: { allow_no_indices: boolean; }; + type?: string; + rollupIndex?: string; }): Promise; ``` @@ -22,7 +24,7 @@ getFieldsForWildcard(options: { | Parameter | Type | Description | | --- | --- | --- | -| options | {
pattern: string | string[];
metaFields?: string[];
fieldCapsOptions?: {
allow_no_indices: boolean;
};
} | | +| options | {
pattern: string | string[];
metaFields?: string[];
fieldCapsOptions?: {
allow_no_indices: boolean;
};
type?: string;
rollupIndex?: string;
} | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md index e7c331bad64e8..6528b1c213cca 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md @@ -8,7 +8,7 @@ ```typescript start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps): { - indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient) => Promise; }; ``` @@ -22,6 +22,6 @@ start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps): Returns: `{ - indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient) => Promise; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 49cf12486cbb8..8957f6d0f06b4 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -28,6 +28,7 @@ | Function | Description | | --- | --- | +| [getCapabilitiesForRollupIndices(indices)](./kibana-plugin-plugins-data-server.getcapabilitiesforrollupindices.md) | | | [getDefaultSearchParams(uiSettingsClient)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | | [getShardTimeout(config)](./kibana-plugin-plugins-data-server.getshardtimeout.md) | | | [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | | @@ -77,6 +78,7 @@ | [esQuery](./kibana-plugin-plugins-data-server.esquery.md) | | | [fieldFormats](./kibana-plugin-plugins-data-server.fieldformats.md) | | | [indexPatterns](./kibana-plugin-plugins-data-server.indexpatterns.md) | | +| [mergeCapabilitiesWithFields](./kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md) | | | [search](./kibana-plugin-plugins-data-server.search.md) | | | [UI\_SETTINGS](./kibana-plugin-plugins-data-server.ui_settings.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md new file mode 100644 index 0000000000000..2880e2d0d8f2c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [mergeCapabilitiesWithFields](./kibana-plugin-plugins-data-server.mergecapabilitieswithfields.md) + +## mergeCapabilitiesWithFields variable + +Signature: + +```typescript +mergeCapabilitiesWithFields: (rollupIndexCapabilities: { + [key: string]: any; +}, fieldsFromFieldCapsApi: { + [key: string]: any; +}, previousFields?: FieldDescriptor[]) => FieldDescriptor[] +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 555a5c2374da7..8a3dbe5a6350c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index aae9b89cdc61f..19c6e9c7b8a7a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -98,11 +98,12 @@ export class IndexPatternsService { * Refresh cache of index pattern ids and titles */ private async refreshSavedObjectsCache() { - this.savedObjectsCache = await this.savedObjectsClient.find({ + const so = await this.savedObjectsClient.find({ type: 'index-pattern', fields: ['title'], perPage: 10000, }); + this.savedObjectsCache = so; } /** @@ -215,13 +216,13 @@ export class IndexPatternsService { * Get field list by providing { pattern } * @param options */ - getFieldsForWildcard = async (options: GetFieldsOptions = {}) => { + getFieldsForWildcard = async (options: GetFieldsOptions) => { const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); return this.apiClient.getFieldsForWildcard({ pattern: options.pattern, metaFields, type: options.type, - params: options.params || {}, + rollupIndex: options.rollupIndex, }); }; @@ -231,13 +232,13 @@ export class IndexPatternsService { */ getFieldsForIndexPattern = async ( indexPattern: IndexPattern | IndexPatternSpec, - options: GetFieldsOptions = {} + options?: GetFieldsOptions ) => this.getFieldsForWildcard({ - pattern: indexPattern.title as string, - ...options, type: indexPattern.type, - params: indexPattern.typeMeta && indexPattern.typeMeta.params, + rollupIndex: indexPattern?.typeMeta?.params?.rollup_index, + ...options, + pattern: indexPattern.title as string, }); /** @@ -374,10 +375,10 @@ export class IndexPatternsService { try { spec.fields = isFieldRefreshRequired ? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { - pattern: title, + pattern: title as string, metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), type, - params: typeMeta && typeMeta.params, + rollupIndex: typeMeta?.params?.rollupIndex, }) : spec.fields; } catch (err) { diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 3387bc3b3c19e..b381cc0963333 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -86,15 +86,22 @@ export interface SavedObjectsClientCommon { } export interface GetFieldsOptions { - pattern?: string; + pattern: string; type?: string; - params?: any; lookBack?: boolean; metaFields?: string[]; + rollupIndex?: string; +} + +export interface GetFieldsOptionsTimePattern { + pattern: string; + metaFields: string[]; + lookBack: number; + interval: string; } export interface IIndexPatternsApiClient { - getFieldsForTimePattern: (options: GetFieldsOptions) => Promise; + getFieldsForTimePattern: (options: GetFieldsOptionsTimePattern) => Promise; getFieldsForWildcard: (options: GetFieldsOptions) => Promise; } diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts index 37ee80c2c29e4..8c48ee44fba9c 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts @@ -32,7 +32,12 @@ describe('IndexPatternsApiClient', () => { test('uses the right URI to fetch fields for time patterns', async function () { const expectedPath = '/api/index_patterns/_fields_for_time_pattern'; - await indexPatternsApiClient.getFieldsForTimePattern(); + await indexPatternsApiClient.getFieldsForTimePattern({ + pattern: 'blah', + metaFields: [], + lookBack: 5, + interval: '', + }); expect(fetchSpy).toHaveBeenCalledWith(expectedPath, expect.any(Object)); }); @@ -40,15 +45,7 @@ describe('IndexPatternsApiClient', () => { test('uses the right URI to fetch fields for wildcard', async function () { const expectedPath = '/api/index_patterns/_fields_for_wildcard'; - await indexPatternsApiClient.getFieldsForWildcard(); - - expect(fetchSpy).toHaveBeenCalledWith(expectedPath, expect.any(Object)); - }); - - test('uses the right URI to fetch fields for wildcard given a type', async function () { - const expectedPath = '/api/index_patterns/rollup/_fields_for_wildcard'; - - await indexPatternsApiClient.getFieldsForWildcard({ type: 'rollup' }); + await indexPatternsApiClient.getFieldsForWildcard({ pattern: 'blah' }); expect(fetchSpy).toHaveBeenCalledWith(expectedPath, expect.any(Object)); }); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 377a3f7f91a50..ca0f35d6612b2 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -19,7 +19,11 @@ import { HttpSetup } from 'src/core/public'; import { IndexPatternMissingIndices } from '../../../common/index_patterns/lib'; -import { GetFieldsOptions, IIndexPatternsApiClient } from '../../../common/index_patterns/types'; +import { + GetFieldsOptions, + IIndexPatternsApiClient, + GetFieldsOptionsTimePattern, +} from '../../../common/index_patterns/types'; const API_BASE_URL: string = `/api/index_patterns/`; @@ -48,7 +52,7 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient { return API_BASE_URL + path.filter(Boolean).map(encodeURIComponent).join('/'); } - getFieldsForTimePattern(options: GetFieldsOptions = {}) { + getFieldsForTimePattern(options: GetFieldsOptionsTimePattern) { const { pattern, lookBack, metaFields } = options; const url = this._getUrl(['_fields_for_time_pattern']); @@ -60,27 +64,12 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient { }).then((resp: any) => resp.fields); } - getFieldsForWildcard(options: GetFieldsOptions = {}) { - const { pattern, metaFields, type, params } = options; - - let url; - let query; - - if (type) { - url = this._getUrl([type, '_fields_for_wildcard']); - query = { - pattern, - meta_fields: metaFields, - params: JSON.stringify(params), - }; - } else { - url = this._getUrl(['_fields_for_wildcard']); - query = { - pattern, - meta_fields: metaFields, - }; - } - - return this._request(url, query).then((resp: any) => resp.fields); + getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { + return this._request(this._getUrl(['_fields_for_wildcard']), { + pattern, + meta_fields: metaFields, + type, + rollup_index: rollupIndex, + }).then((resp: any) => resp.fields); } } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index bc2bb2663f163..31b05bd4763a2 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1367,9 +1367,9 @@ export class IndexPatternsService { // (undocumented) getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; - getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => 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 - getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; + getFieldsForWildcard: (options: GetFieldsOptions) => Promise; getIds: (refresh?: boolean) => Promise; getIdsWithTitle: (refresh?: boolean) => Promise { - const { pattern, metaFields, fieldCapsOptions } = options; - return await getFieldCapabilities(this.elasticsearchClient, pattern, metaFields, { - allow_no_indices: fieldCapsOptions ? fieldCapsOptions.allow_no_indices : this.allowNoIndices, - }); + const { pattern, metaFields, fieldCapsOptions, type, rollupIndex } = options; + const fieldCapsResponse = await getFieldCapabilities( + this.elasticsearchClient, + pattern, + metaFields, + { + allow_no_indices: fieldCapsOptions + ? fieldCapsOptions.allow_no_indices + : this.allowNoIndices, + } + ); + if (type === 'rollup' && rollupIndex) { + const rollupFields: FieldDescriptor[] = []; + const rollupIndexCapabilities = getCapabilitiesForRollupIndices( + ( + await this.elasticsearchClient.rollup.getRollupIndexCaps({ + index: rollupIndex, + }) + ).body + )[rollupIndex].aggs; + const fieldCapsResponseObj = keyBy(fieldCapsResponse, 'name'); + + // Keep meta fields + metaFields!.forEach( + (field: string) => + fieldCapsResponseObj[field] && rollupFields.push(fieldCapsResponseObj[field]) + ); + + return mergeCapabilitiesWithFields( + rollupIndexCapabilities, + fieldCapsResponseObj, + rollupFields + ); + } + return fieldCapsResponse; } /** diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/index.js b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/index.js new file mode 100644 index 0000000000000..d675702ae54e9 --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/index.js @@ -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 { jobs } from './jobs'; diff --git a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/jobs.js similarity index 65% rename from x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js rename to src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/jobs.js index c03b7c33abe0a..39ebd9595eeaf 100644 --- a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/fixtures/jobs.js @@ -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. */ export const jobs = [ diff --git a/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/jobs_compatibility.js similarity index 81% rename from x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js rename to src/plugins/data/server/index_patterns/fetcher/lib/__tests__/jobs_compatibility.js index a67f67de859f5..e3c93ac1f8616 100644 --- a/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/__tests__/jobs_compatibility.js @@ -1,8 +1,22 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * 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 expect from '@kbn/expect'; import { areJobsCompatible, mergeJobConfigurations } from '../jobs_compatibility'; import { jobs } from './fixtures'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/index.ts b/src/plugins/data/server/index_patterns/fetcher/lib/index.ts index 20e74d2b1a579..b2fd3a1a09a25 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/index.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/index.ts @@ -20,3 +20,5 @@ export { getFieldCapabilities, shouldReadFieldFromDocValues } from './field_capabilities'; export { resolveTimePattern } from './resolve_time_pattern'; export { createNoMatchingIndicesError } from './errors'; +export * from './merge_capabilities_with_fields'; +export * from './map_capabilities'; diff --git a/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts b/src/plugins/data/server/index_patterns/fetcher/lib/jobs_compatibility.ts similarity index 79% rename from x-pack/plugins/rollup/server/lib/jobs_compatibility.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/jobs_compatibility.ts index f5f54cf9a54e8..f21de8907ee24 100644 --- a/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/jobs_compatibility.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 { isEqual } from 'lodash'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/map_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/map_capabilities.ts new file mode 100644 index 0000000000000..6187174834012 --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/lib/map_capabilities.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 { mergeJobConfigurations } from './jobs_compatibility'; + +export function getCapabilitiesForRollupIndices(indices: { [key: string]: any }) { + const indexNames = Object.keys(indices); + const capabilities = {} as { [key: string]: any }; + + indexNames.forEach((index) => { + try { + capabilities[index] = mergeJobConfigurations(indices[index].rollup_jobs); + } catch (e) { + capabilities[index] = { + error: e.message, + }; + } + }); + + return capabilities; +} diff --git a/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts b/src/plugins/data/server/index_patterns/fetcher/lib/merge_capabilities_with_fields.ts similarity index 70% rename from x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts rename to src/plugins/data/server/index_patterns/fetcher/lib/merge_capabilities_with_fields.ts index 51111e9e45d0a..dd69f4b7ff007 100644 --- a/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/merge_capabilities_with_fields.ts @@ -1,20 +1,30 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * 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. */ // Merge rollup capabilities information with field information -export interface Field { - name?: string; - [key: string]: any; -} +import { FieldDescriptor } from '../index_patterns_fetcher'; export const mergeCapabilitiesWithFields = ( rollupIndexCapabilities: { [key: string]: any }, fieldsFromFieldCapsApi: { [key: string]: any }, - previousFields: Field[] = [] + previousFields: FieldDescriptor[] = [] ) => { const rollupFields = [...previousFields]; const rollupFieldNames: string[] = []; diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts index 683d1c445fd72..3305b1bb9a92f 100644 --- a/src/plugins/data/server/index_patterns/index.ts +++ b/src/plugins/data/server/index_patterns/index.ts @@ -17,5 +17,11 @@ * under the License. */ export * from './utils'; -export { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher'; +export { + IndexPatternsFetcher, + FieldDescriptor, + shouldReadFieldFromDocValues, + mergeCapabilitiesWithFields, + getCapabilitiesForRollupIndices, +} from './fetcher'; export { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns_service'; diff --git a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts index 2dc6f40c5a6f1..21a3bf6e73e61 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts @@ -17,13 +17,30 @@ * under the License. */ -import { GetFieldsOptions, IIndexPatternsApiClient } from '../../common/index_patterns/types'; +import { ElasticsearchClient } from 'kibana/server'; +import { + GetFieldsOptions, + IIndexPatternsApiClient, + GetFieldsOptionsTimePattern, +} from '../../common/index_patterns/types'; +import { IndexPatternsFetcher } from './fetcher'; export class IndexPatternsApiServer implements IIndexPatternsApiClient { - async getFieldsForTimePattern(options: GetFieldsOptions = {}) { - throw new Error('IndexPatternsApiServer - getFieldsForTimePattern not defined'); + esClient: ElasticsearchClient; + constructor(elasticsearchClient: ElasticsearchClient) { + this.esClient = elasticsearchClient; } - async getFieldsForWildcard(options: GetFieldsOptions = {}) { - throw new Error('IndexPatternsApiServer - getFieldsForWildcard not defined'); + async getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { + const indexPatterns = new IndexPatternsFetcher(this.esClient); + return await indexPatterns.getFieldsForWildcard({ + pattern, + metaFields, + type, + rollupIndex, + }); + } + async getFieldsForTimePattern(options: GetFieldsOptionsTimePattern) { + const indexPatterns = new IndexPatternsFetcher(this.esClient); + return await indexPatterns.getFieldsForTimePattern(options); } } diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index d665e3715fa72..af2d4d6a73e0f 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -17,7 +17,14 @@ * under the License. */ -import { CoreSetup, CoreStart, Plugin, Logger, SavedObjectsClientContract } from 'kibana/server'; +import { + CoreSetup, + CoreStart, + Plugin, + Logger, + SavedObjectsClientContract, + ElasticsearchClient, +} from 'kibana/server'; import { registerRoutes } from './routes'; import { indexPatternSavedObjectType } from '../saved_objects'; import { capabilitiesProvider } from './capabilities_provider'; @@ -29,7 +36,8 @@ import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper export interface IndexPatternsServiceStart { indexPatternsServiceFactory: ( - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient ) => Promise; } @@ -50,14 +58,17 @@ export class IndexPatternsService implements Plugin { + indexPatternsServiceFactory: async ( + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient + ) => { const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); return new IndexPatternsCommonService({ uiSettings: new UiSettingsServerToCommon(uiSettingsClient), savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient), - apiClient: new IndexPatternsApiServer(), + apiClient: new IndexPatternsApiServer(elasticsearchClient), fieldFormats: formats, onError: (error) => { logger.error(error); diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 041eb235d01e0..f8af52954fc61 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -42,13 +42,15 @@ export function registerRoutes(http: HttpServiceSetup) { meta_fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { defaultValue: [], }), + type: schema.maybe(schema.string()), + rollup_index: schema.maybe(schema.string()), }), }, }, async (context, request, response) => { const { asCurrentUser } = context.core.elasticsearch.client; const indexPatterns = new IndexPatternsFetcher(asCurrentUser); - const { pattern, meta_fields: metaFields } = request.query; + const { pattern, meta_fields: metaFields, type, rollup_index: rollupIndex } = request.query; let parsedFields: string[] = []; try { @@ -61,6 +63,8 @@ export function registerRoutes(http: HttpServiceSetup) { const fields = await indexPatterns.getFieldsForWildcard({ pattern, metaFields: parsedFields, + type, + rollupIndex, }); return response.ok({ diff --git a/src/plugins/data/server/search/aggs/aggs_service.test.ts b/src/plugins/data/server/search/aggs/aggs_service.test.ts index cb4239cc339c4..e58420f6c2f07 100644 --- a/src/plugins/data/server/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/server/search/aggs/aggs_service.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { KibanaRequest } from 'src/core/server'; +import { KibanaRequest, ElasticsearchClient } from 'src/core/server'; import { coreMock } from '../../../../../core/server/mocks'; import { expressionsPluginMock } from '../../../../../plugins/expressions/server/mocks'; @@ -63,7 +63,8 @@ describe('AggsService - server', () => { expect(start).toHaveProperty('asScopedToClient'); const contract = await start.asScopedToClient( - savedObjects.getScopedClient({} as KibanaRequest) + savedObjects.getScopedClient({} as KibanaRequest), + {} as ElasticsearchClient ); expect(contract).toHaveProperty('calculateAutoTimeExpression'); expect(contract).toHaveProperty('createAggConfigs'); @@ -74,7 +75,10 @@ describe('AggsService - server', () => { service.setup(setupDeps); const start = await service .start(startDeps) - .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest)); + .asScopedToClient( + savedObjects.getScopedClient({} as KibanaRequest), + {} as ElasticsearchClient + ); expect(start.types.get('terms').name).toBe('terms'); }); @@ -83,7 +87,10 @@ describe('AggsService - server', () => { service.setup(setupDeps); const start = await service .start(startDeps) - .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest)); + .asScopedToClient( + savedObjects.getScopedClient({} as KibanaRequest), + {} as ElasticsearchClient + ); const aggTypes = getAggTypes(); expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length); @@ -103,7 +110,10 @@ describe('AggsService - server', () => { const start = await service .start(startDeps) - .asScopedToClient(savedObjects.getScopedClient({} as KibanaRequest)); + .asScopedToClient( + savedObjects.getScopedClient({} as KibanaRequest), + {} as ElasticsearchClient + ); const aggTypes = getAggTypes(); expect(start.types.getAll().buckets.length).toBe(aggTypes.buckets.length + 1); diff --git a/src/plugins/data/server/search/aggs/aggs_service.ts b/src/plugins/data/server/search/aggs/aggs_service.ts index c805c8af6694c..c23f748b1eeb5 100644 --- a/src/plugins/data/server/search/aggs/aggs_service.ts +++ b/src/plugins/data/server/search/aggs/aggs_service.ts @@ -19,7 +19,11 @@ import { pick } from 'lodash'; -import { UiSettingsServiceStart, SavedObjectsClientContract } from 'src/core/server'; +import { + UiSettingsServiceStart, + SavedObjectsClientContract, + ElasticsearchClient, +} from 'src/core/server'; import { ExpressionsServiceSetup } from 'src/plugins/expressions/common'; import { AggsCommonService, @@ -65,7 +69,10 @@ export class AggsService { public start({ fieldFormats, uiSettings, indexPatterns }: AggsStartDependencies): AggsStart { return { - asScopedToClient: async (savedObjectsClient: SavedObjectsClientContract) => { + asScopedToClient: async ( + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient + ) => { const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); @@ -82,8 +89,9 @@ export class AggsService { types, } = this.aggsCommonService.start({ getConfig, - getIndexPattern: (await indexPatterns.indexPatternsServiceFactory(savedObjectsClient)) - .get, + getIndexPattern: ( + await indexPatterns.indexPatternsServiceFactory(savedObjectsClient, elasticsearchClient) + ).get, isDefaultTimezone, }); diff --git a/src/plugins/data/server/search/aggs/types.ts b/src/plugins/data/server/search/aggs/types.ts index 1b21d948b25d9..2c28c970cbb84 100644 --- a/src/plugins/data/server/search/aggs/types.ts +++ b/src/plugins/data/server/search/aggs/types.ts @@ -17,11 +17,14 @@ * under the License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server'; import { AggsCommonSetup, AggsStart as Start } from '../../../common'; export type AggsSetup = AggsCommonSetup; export interface AggsStart { - asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => Promise; + asScopedToClient: ( + savedObjectsClient: SavedObjectsClientContract, + elasticsearchClient: ElasticsearchClient + ) => Promise; } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index c5a60c42a41c9..d8aa588719e3e 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -177,7 +177,11 @@ export class SearchService implements Plugin { const { elasticsearch, savedObjects, uiSettings } = core; const asScoped = this.asScopedProvider(core); return { - aggs: this.aggsService.start({ fieldFormats, uiSettings, indexPatterns }), + aggs: this.aggsService.start({ + fieldFormats, + uiSettings, + indexPatterns, + }), getSearchStrategy: this.getSearchStrategy, asScoped, searchSource: { @@ -185,7 +189,8 @@ export class SearchService implements Plugin { const esClient = elasticsearch.client.asScoped(request); const savedObjectsClient = savedObjects.getScopedClient(request); const scopedIndexPatterns = await indexPatterns.indexPatternsServiceFactory( - savedObjectsClient + savedObjectsClient, + esClient.asCurrentUser ); const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 5b0e0499e29bf..131b3e4c39c6b 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -17,7 +17,8 @@ import { CoreStart as CoreStart_2 } from 'kibana/server'; import { Datatable } from 'src/plugins/expressions/common'; import { DatatableColumn } from 'src/plugins/expressions'; import { Duration } from 'moment'; -import { ElasticsearchClient } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; +import { ElasticsearchClient as ElasticsearchClient_2 } from 'kibana/server'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; @@ -389,6 +390,15 @@ export type Filter = { query?: any; }; +// Warning: (ae-missing-release-tag) "getCapabilitiesForRollupIndices" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getCapabilitiesForRollupIndices(indices: { + [key: string]: any; +}): { + [key: string]: any; +}; + // Warning: (ae-forgotten-export) The symbol "IUiSettingsClient" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getDefaultSearchParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -688,7 +698,7 @@ export const indexPatterns: { // // @public (undocumented) export class IndexPatternsFetcher { - constructor(elasticsearchClient: ElasticsearchClient, allowNoIndices?: boolean); + constructor(elasticsearchClient: ElasticsearchClient_2, allowNoIndices?: boolean); getFieldsForTimePattern(options: { pattern: string; metaFields: string[]; @@ -701,6 +711,8 @@ export class IndexPatternsFetcher { fieldCapsOptions?: { allow_no_indices: boolean; }; + type?: string; + rollupIndex?: string; }): Promise; } @@ -715,7 +727,7 @@ export class IndexPatternsService implements Plugin_3 Promise; + indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract_2, elasticsearchClient: ElasticsearchClient_2) => Promise; }; } @@ -824,6 +836,15 @@ export interface KueryNode { type: keyof NodeTypes; } +// Warning: (ae-missing-release-tag) "mergeCapabilitiesWithFields" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const mergeCapabilitiesWithFields: (rollupIndexCapabilities: { + [key: string]: any; +}, fieldsFromFieldCapsApi: { + [key: string]: any; +}, previousFields?: FieldDescriptor[]) => FieldDescriptor[]; + // Warning: (ae-missing-release-tag) "METRIC_TYPES" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -927,7 +948,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -1195,22 +1216,22 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:256:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:257:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:272:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:273:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:277:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:278:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:282:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:285:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:258:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:259:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:263:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:273:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:274:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:275:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:279:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:280:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:284:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:287:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index_patterns/index_patterns_service.ts:58:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // src/plugins/data/server/search/types.ts:104:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index b52188129f77f..dc49e280a2bb7 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -62,10 +62,12 @@ export async function getFields( let indexPatternString = indexPattern; if (!indexPatternString) { - const [{ savedObjects }, { data }] = await framework.core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await framework.core.getStartServices(); const savedObjectsClient = savedObjects.getScopedClient(request); + const clusterClient = elasticsearch.client.asScoped(request).asCurrentUser; const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( - savedObjectsClient + savedObjectsClient, + clusterClient ); const defaultIndexPattern = await indexPatternsService.getDefault(); indexPatternString = get(defaultIndexPattern, 'title', ''); diff --git a/test/plugin_functional/plugins/data_search/server/plugin.ts b/test/plugin_functional/plugins/data_search/server/plugin.ts index e016ef56802f3..ca22e82188403 100644 --- a/test/plugin_functional/plugins/data_search/server/plugin.ts +++ b/test/plugin_functional/plugins/data_search/server/plugin.ts @@ -58,14 +58,16 @@ export class DataSearchTestPlugin }, }, async (context, req, res) => { - const [{ savedObjects }, { data }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); const service = await data.search.searchSource.asScoped(req); + const clusterClient = elasticsearch.client.asScoped(req).asCurrentUser; const savedObjectsClient = savedObjects.getScopedClient(req); // Since the index pattern ID can change on each test run, we need // to look it up on the fly and insert it into the request. const indexPatterns = await data.indexPatterns.indexPatternsServiceFactory( - savedObjectsClient + savedObjectsClient, + clusterClient ); const ids = await indexPatterns.getIds(); // @ts-expect-error Force overwriting the request diff --git a/test/plugin_functional/plugins/index_patterns/server/plugin.ts b/test/plugin_functional/plugins/index_patterns/server/plugin.ts index a54502b740211..c05b71b834c70 100644 --- a/test/plugin_functional/plugins/index_patterns/server/plugin.ts +++ b/test/plugin_functional/plugins/index_patterns/server/plugin.ts @@ -36,13 +36,35 @@ export class IndexPatternsTestPlugin public setup(core: CoreSetup) { const router = core.http.createRouter(); + router.post( + { + path: '/api/index-patterns-plugin/create', + validate: { + body: schema.object({}, { unknowns: 'allow' }), + }, + }, + async (context, req, res) => { + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); + const savedObjectsClient = savedObjects.getScopedClient(req); + const service = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearch.client.asScoped(req).asCurrentUser + ); + const ids = await service.createAndSave(req.body); + return res.ok({ body: ids }); + } + ); + router.get( { path: '/api/index-patterns-plugin/get-all', validate: false }, async (context, req, res) => { - const [{ savedObjects }, { data }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); const savedObjectsClient = savedObjects.getScopedClient(req); - const service = await data.indexPatterns.indexPatternsServiceFactory(savedObjectsClient); - const ids = await service.getIds(); + const service = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearch.client.asScoped(req).asCurrentUser + ); + const ids = await service.getIds(true); return res.ok({ body: ids }); } ); @@ -58,9 +80,12 @@ export class IndexPatternsTestPlugin }, async (context, req, res) => { const id = (req.params as Record).id; - const [{ savedObjects }, { data }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); const savedObjectsClient = savedObjects.getScopedClient(req); - const service = await data.indexPatterns.indexPatternsServiceFactory(savedObjectsClient); + const service = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearch.client.asScoped(req).asCurrentUser + ); const ip = await service.get(id); return res.ok({ body: ip.toSpec() }); } @@ -76,10 +101,13 @@ export class IndexPatternsTestPlugin }, }, async (context, req, res) => { - const [{ savedObjects }, { data }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); const id = (req.params as Record).id; const savedObjectsClient = savedObjects.getScopedClient(req); - const service = await data.indexPatterns.indexPatternsServiceFactory(savedObjectsClient); + const service = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearch.client.asScoped(req).asCurrentUser + ); const ip = await service.get(id); await service.updateSavedObject(ip); return res.ok(); @@ -96,10 +124,13 @@ export class IndexPatternsTestPlugin }, }, async (context, req, res) => { - const [{ savedObjects }, { data }] = await core.getStartServices(); + const [{ savedObjects, elasticsearch }, { data }] = await core.getStartServices(); const id = (req.params as Record).id; const savedObjectsClient = savedObjects.getScopedClient(req); - const service = await data.indexPatterns.indexPatternsServiceFactory(savedObjectsClient); + const service = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + elasticsearch.client.asScoped(req).asCurrentUser + ); await service.delete(id); return res.ok(); } diff --git a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts index 2c846dc780311..2e94f61c7ded8 100644 --- a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts +++ b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts @@ -23,16 +23,25 @@ import '../../plugins/core_provider_plugin/types'; export default function ({ getService }: PluginFunctionalProviderContext) { const supertest = getService('supertest'); - // skipping the tests as it deletes index patterns created by other test causing unexpected failures - // https://github.com/elastic/kibana/issues/79886 - describe.skip('index patterns', function () { + describe('index patterns', function () { let indexPatternId = ''; - it('can get all ids', async () => { - const body = await (await supertest.get('/api/index-patterns-plugin/get-all').expect(200)) - .body; - indexPatternId = body[0]; - expect(body.length > 0).to.equal(true); + it('can create an index pattern', async () => { + const title = 'shakes*'; + const fieldFormats = { bytes: { id: 'bytes' } }; + const body = await ( + await supertest + .post('/api/index-patterns-plugin/create') + .set('kbn-xsrf', 'anything') + .send({ title, fieldFormats }) + .expect(200) + ).body; + + indexPatternId = body.id; + expect(body.id).not.empty(); + expect(body.title).to.equal(title); + expect(body.fields.length).to.equal(15); + expect(body.fieldFormatMap).to.eql(fieldFormats); }); it('can get index pattern by id', async () => { diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index 643cc3efb0136..e021831d2f438 100644 --- a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -161,9 +161,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig getFetchForWildcardOptions = () => { return { type: this.type, - params: { - rollup_index: this.rollupIndex, - }, + rollupIndex: this.rollupIndex, }; }; } diff --git a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js deleted file mode 100644 index e97606c1fadfb..0000000000000 --- a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { jobs } from './jobs'; diff --git a/x-pack/plugins/rollup/server/lib/map_capabilities.ts b/x-pack/plugins/rollup/server/lib/map_capabilities.ts deleted file mode 100644 index 233c6d1dd4b4b..0000000000000 --- a/x-pack/plugins/rollup/server/lib/map_capabilities.ts +++ /dev/null @@ -1,24 +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 { mergeJobConfigurations } from './jobs_compatibility'; - -export function getCapabilitiesForRollupIndices(indices: { [key: string]: any }) { - const indexNames = Object.keys(indices); - const capabilities = {} as { [key: string]: any }; - - indexNames.forEach((index) => { - try { - capabilities[index] = mergeJobConfigurations(indices[index].rollup_jobs); - } catch (e) { - capabilities[index] = { - error: e.message, - }; - } - }); - - return capabilities; -} diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts index f439ac555aed9..dcf6629d35397 100644 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts @@ -6,8 +6,11 @@ import { keyBy, isString } from 'lodash'; import { ILegacyScopedClusterClient } from 'src/core/server'; import { ReqFacade } from '../../../../../../src/plugins/vis_type_timeseries/server'; -import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; -import { getCapabilitiesForRollupIndices } from '../map_capabilities'; + +import { + mergeCapabilitiesWithFields, + getCapabilitiesForRollupIndices, +} from '../../../../../../src/plugins/data/server'; const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData); diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index fe193150fc1ca..51920af7c8cbc 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -36,8 +36,7 @@ import { registerRollupSearchStrategy } from './lib/search_strategies'; import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; import { isEsError } from './shared_imports'; import { formatEsError } from './lib/format_es_error'; -import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; -import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; +import { getCapabilitiesForRollupIndices } from '../../../../src/plugins/data/server'; interface RollupContext { client: ILegacyScopedClusterClient; @@ -107,7 +106,6 @@ export class RollupPlugin implements Plugin { isEsError, formatEsError, getCapabilitiesForRollupIndices, - mergeCapabilitiesWithFields, }, sharedImports: { IndexPatternsFetcher, diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts deleted file mode 100644 index 7bf525ca4aa98..0000000000000 --- a/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts +++ /dev/null @@ -1,12 +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 { RouteDependencies } from '../../../types'; -import { registerFieldsForWildcardRoute } from './register_fields_for_wildcard_route'; - -export function registerIndexPatternsRoutes(dependencies: RouteDependencies) { - registerFieldsForWildcardRoute(dependencies); -} diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts deleted file mode 100644 index df9907fbf731a..0000000000000 --- a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts +++ /dev/null @@ -1,142 +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 { keyBy } from 'lodash'; -import { schema } from '@kbn/config-schema'; -import { Field } from '../../../lib/merge_capabilities_with_fields'; -import { RouteDependencies } from '../../../types'; -import type { IndexPatternsFetcher as IndexPatternsFetcherType } from '../../../../../../../src/plugins/data/server'; - -const parseMetaFields = (metaFields: string | string[]) => { - let parsedFields: string[] = []; - if (typeof metaFields === 'string') { - parsedFields = JSON.parse(metaFields); - } else { - parsedFields = metaFields; - } - return parsedFields; -}; - -const getFieldsForWildcardRequest = async ( - context: any, - request: any, - response: any, - IndexPatternsFetcher: typeof IndexPatternsFetcherType -) => { - const { asCurrentUser } = context.core.elasticsearch.client; - const indexPatterns = new IndexPatternsFetcher(asCurrentUser); - const { pattern, meta_fields: metaFields } = request.query; - - let parsedFields: string[] = []; - try { - parsedFields = parseMetaFields(metaFields); - } catch (error) { - return response.badRequest({ - body: error, - }); - } - - try { - const fields = await indexPatterns.getFieldsForWildcard({ - pattern, - metaFields: parsedFields, - }); - - return response.ok({ - body: { fields }, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - return response.notFound(); - } -}; - -/** - * Get list of fields for rollup index pattern, in the format of regular index pattern fields - */ -export const registerFieldsForWildcardRoute = ({ - router, - license, - lib: { isEsError, formatEsError, getCapabilitiesForRollupIndices, mergeCapabilitiesWithFields }, - sharedImports: { IndexPatternsFetcher }, -}: RouteDependencies) => { - const querySchema = schema.object({ - pattern: schema.string(), - meta_fields: schema.arrayOf(schema.string(), { - defaultValue: [], - }), - params: schema.string({ - validate(value) { - try { - const params = JSON.parse(value); - const keys = Object.keys(params); - const { rollup_index: rollupIndex } = params; - - if (!rollupIndex) { - return '[request query.params]: "rollup_index" is required'; - } else if (keys.length > 1) { - const invalidParams = keys.filter((key) => key !== 'rollup_index'); - return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; - } - } catch (err) { - return '[request query.params]: expected JSON string'; - } - }, - }), - }); - - router.get( - { - path: '/api/index_patterns/rollup/_fields_for_wildcard', - validate: { - query: querySchema, - }, - }, - license.guardApiRoute(async (context, request, response) => { - const { params, meta_fields: metaFields } = request.query; - - try { - // Make call and use field information from response - const { payload } = await getFieldsForWildcardRequest( - context, - request, - response, - IndexPatternsFetcher - ); - const fields = payload.fields; - const parsedParams = JSON.parse(params); - const rollupIndex = parsedParams.rollup_index; - const rollupFields: Field[] = []; - const fieldsFromFieldCapsApi: { [key: string]: any } = keyBy(fields, 'name'); - const rollupIndexCapabilities = getCapabilitiesForRollupIndices( - await context.rollup!.client.callAsCurrentUser('rollup.rollupIndexCapabilities', { - indexPattern: rollupIndex, - }) - )[rollupIndex].aggs; - - // Keep meta fields - metaFields.forEach( - (field: string) => - fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) - ); - - const mergedRollupFields = mergeCapabilitiesWithFields( - rollupIndexCapabilities, - fieldsFromFieldCapsApi, - rollupFields - ); - return response.ok({ body: { fields: mergedRollupFields } }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }) - ); -}; diff --git a/x-pack/plugins/rollup/server/routes/index.ts b/x-pack/plugins/rollup/server/routes/index.ts index b25480855b4a2..322003c0ee325 100644 --- a/x-pack/plugins/rollup/server/routes/index.ts +++ b/x-pack/plugins/rollup/server/routes/index.ts @@ -6,13 +6,11 @@ import { RouteDependencies } from '../types'; -import { registerIndexPatternsRoutes } from './api/index_patterns'; import { registerIndicesRoutes } from './api/indices'; import { registerJobsRoutes } from './api/jobs'; import { registerSearchRoutes } from './api/search'; export function registerApiRoutes(dependencies: RouteDependencies) { - registerIndexPatternsRoutes(dependencies); registerIndicesRoutes(dependencies); registerJobsRoutes(dependencies); registerSearchRoutes(dependencies); diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index b167806cf8d5d..89e13e69c4da2 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -8,6 +8,7 @@ import { IRouter } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; +import { getCapabilitiesForRollupIndices } from 'src/plugins/data/server'; import { IndexManagementPluginSetup } from '../../index_management/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; @@ -15,8 +16,6 @@ import { License } from './services'; import { IndexPatternsFetcher } from './shared_imports'; import { isEsError } from './shared_imports'; import { formatEsError } from './lib/format_es_error'; -import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; -import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; export interface Dependencies { indexManagement?: IndexManagementPluginSetup; @@ -33,7 +32,6 @@ export interface RouteDependencies { isEsError: typeof isEsError; formatEsError: typeof formatEsError; getCapabilitiesForRollupIndices: typeof getCapabilitiesForRollupIndices; - mergeCapabilitiesWithFields: typeof mergeCapabilitiesWithFields; }; sharedImports: { IndexPatternsFetcher: typeof IndexPatternsFetcher; diff --git a/x-pack/test/api_integration/apis/management/rollup/constants.js b/x-pack/test/api_integration/apis/management/rollup/constants.js index fe899c4c10c88..0313434cf716c 100644 --- a/x-pack/test/api_integration/apis/management/rollup/constants.js +++ b/x-pack/test/api_integration/apis/management/rollup/constants.js @@ -5,7 +5,7 @@ */ export const API_BASE_PATH = '/api/rollup'; -export const INDEX_PATTERNS_EXTENSION_BASE_PATH = '/api/index_patterns/rollup'; +export const INDEX_PATTERNS_EXTENSION_BASE_PATH = '/api/index_patterns'; export const ROLLUP_INDEX_NAME = 'rollup_index'; export const INDEX_TO_ROLLUP_MAPPINGS = { properties: { diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index 357b952e7e66d..0a93e8b8bd1e3 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -26,7 +26,6 @@ export default function ({ getService }) { describe('query params validation', () => { let uri; let body; - let params; it('"pattern" is required', async () => { uri = `${BASE_URI}`; @@ -36,62 +35,17 @@ export default function ({ getService }) { ); }); - it('"params" is required', async () => { - params = { pattern: 'foo' }; - uri = `${BASE_URI}?${stringify(params, { sort: false })}`; - ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain( - '[request query.params]: expected value of type [string]' - ); - }); - - it('"params" must be a valid JSON string', async () => { - params = { pattern: 'foo', params: 'foobarbaz' }; - uri = `${BASE_URI}?${stringify(params, { sort: false })}`; - ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('[request query.params]: expected JSON string'); - }); - - it('"params" requires a "rollup_index" property', async () => { - params = { pattern: 'foo', params: JSON.stringify({}) }; - uri = `${BASE_URI}?${stringify(params, { sort: false })}`; - ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('[request query.params]: "rollup_index" is required'); - }); - - it('"params" only accepts a "rollup_index" property', async () => { - params = { - pattern: 'foo', - params: JSON.stringify({ rollup_index: 'my_index', someProp: 'bar' }), - }; - uri = `${BASE_URI}?${stringify(params, { sort: false })}`; - ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('[request query.params]: someProp is not allowed'); - }); - - it('"meta_fields" must be an Array', async () => { - params = { - pattern: 'foo', - params: JSON.stringify({ rollup_index: 'bar' }), - meta_fields: 'stringValue', - }; - uri = `${BASE_URI}?${stringify(params, { sort: false })}`; - ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain( - '[request query.meta_fields]: could not parse array value from json input' - ); - }); - it('should return 404 the rollup index to query does not exist', async () => { uri = `${BASE_URI}?${stringify( { pattern: 'foo', - params: JSON.stringify({ rollup_index: 'bar' }), + type: 'rollup', + rollup_index: 'bar', }, { sort: false } )}`; ({ body } = await supertest.get(uri).expect(404)); - expect(body.message).to.contain('[index_not_found_exception] no such index [bar]'); + expect(body.message).to.contain('No indices match pattern "foo"'); }); }); @@ -105,7 +59,8 @@ export default function ({ getService }) { // Query for wildcard const params = { pattern: indexName, - params: JSON.stringify({ rollup_index: rollupIndex }), + type: 'rollup', + rollup_index: rollupIndex, }; const uri = `${BASE_URI}?${stringify(params, { sort: false })}`; const { body } = await supertest.get(uri).expect(200); From f193d9c96271d0ad7c1b3c721a9f82690e4751b6 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Nov 2020 17:33:13 -0700 Subject: [PATCH 37/87] [data.search.aggs] Throw an error when trying to create an agg type that doesn't exist. (#81509) --- .../common/search/aggs/agg_config.test.ts | 10 --------- .../common/search/aggs/agg_configs.test.ts | 21 +++++++++++++++++++ .../data/common/search/aggs/agg_configs.ts | 19 +++++++++++++++-- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/plugins/data/common/search/aggs/agg_config.test.ts b/src/plugins/data/common/search/aggs/agg_config.test.ts index f6fcc29805dc4..9bb47f5cb3575 100644 --- a/src/plugins/data/common/search/aggs/agg_config.test.ts +++ b/src/plugins/data/common/search/aggs/agg_config.test.ts @@ -680,16 +680,6 @@ describe('AggConfig', () => { const json = aggConfig.toExpressionAst()?.arguments.json; expect(json).toEqual([JSON.stringify(configStates.params.json)]); }); - - it(`returns undefined if an expressionName doesn't exist on the agg type`, () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - type: 'unknown type', - params: {}, - }; - const aggConfig = ac.createAggConfig(configStates); - expect(aggConfig.toExpressionAst()).toBe(undefined); - }); }); describe('#makeLabel', () => { diff --git a/src/plugins/data/common/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts index 803ccc70b98a7..d1c8e29a03cc7 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts @@ -150,6 +150,27 @@ describe('AggConfigs', () => { ); expect(ac.aggs).toHaveLength(1); }); + + it(`throws if trying to add an agg which doesn't have a type in the registry`, () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(() => + ac.createAggConfig({ + enabled: true, + type: 'oops', + params: {}, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to find a registered agg type for \\"oops\\"."` + ); + }); }); describe('#getRequestAggs', () => { diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index 282e6f3b538a4..368f44f161132 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; import { Assign } from '@kbn/utility-types'; import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; @@ -122,15 +123,29 @@ export class AggConfigs { { addToAggConfigs = true } = {} ) => { const { type } = params; - let aggConfig; + const getType = (t: string) => { + const typeFromRegistry = this.typesRegistry.get(t); + + if (!typeFromRegistry) { + throw new Error( + i18n.translate('data.search.aggs.error.aggNotFound', { + defaultMessage: 'Unable to find a registered agg type for "{type}".', + values: { type: type as string }, + }) + ); + } + return typeFromRegistry; + }; + + let aggConfig; if (params instanceof AggConfig) { aggConfig = params; params.parent = this; } else { aggConfig = new AggConfig(this, { ...params, - type: typeof type === 'string' ? this.typesRegistry.get(type) : type, + type: typeof type === 'string' ? getType(type) : type, }); } From bafe9dfea1ca7c44b0e1283860659b08467bc3ef Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 10 Nov 2020 10:05:32 +0100 Subject: [PATCH 38/87] [Lens] Distinct icons for XY and pie chart value labels toolbar (#82927) --- x-pack/plugins/lens/public/pie_visualization/toolbar.tsx | 2 +- .../plugins/lens/public/shared_components/toolbar_popover.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index ab7422c3eeb63..d69164de8a6aa 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -125,7 +125,7 @@ export function PieToolbar(props: VisualizationToolbarProps diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx index 20837424dc7b5..cf2268c6eadf2 100644 --- a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx +++ b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx @@ -11,6 +11,7 @@ import { EuiIconLegend } from '../assets/legend'; const typeToIconMap: { [type: string]: string | IconType } = { legend: EuiIconLegend as IconType, + labels: 'visText', values: 'number', }; From 1de3a02a4620ba02151985a622647e6d8c95c3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 10 Nov 2020 11:09:37 +0100 Subject: [PATCH 39/87] Fix ilm navigation (#81664) * Fix edit policy page navigation * Fix edit policy page navigation * Add links to PR for explanation * Added more tests and linked to a github issue about navigation issues * Fix decoding function for undefined values * Fix type check issues * Renamed dollar sign to percent sign, added a method for (double) encoded paths and better description in test names * Deleted Index Management from required bundles in ILM * Fixed merge conflicts * Revert "Deleted Index Management from required bundles in ILM" This reverts commit 5a735dfe Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/url/attempt_to_uri_decode.test.ts | 49 +++- .../public/url/attempt_to_uri_decode.ts | 8 +- .../client_integration/app/app.helpers.tsx | 74 +++++ .../client_integration/app/app.test.ts | 252 ++++++++++++++++++ .../edit_policy/constants.ts | 20 ++ .../edit_policy/edit_policy.test.ts | 3 +- .../client_integration/helpers/index.ts | 3 + .../public/application/app.tsx | 40 +-- .../public/application/index.tsx | 8 +- .../edit_policy/edit_policy.container.tsx | 6 +- .../sections/edit_policy/edit_policy.tsx | 2 +- .../public/shared_imports.ts | 2 + .../component_template_details.tsx | 2 +- .../component_template_list.tsx | 6 +- .../component_template_clone.tsx | 2 +- .../component_template_edit.tsx | 2 +- .../data_stream_list/data_stream_list.tsx | 2 +- .../home/template_list/template_list.tsx | 2 +- .../template_clone/template_clone.tsx | 2 +- .../sections/template_edit/template_edit.tsx | 2 +- .../pipelines_clone/pipelines_clone.tsx | 2 +- .../pipelines_edit/pipelines_edit.tsx | 2 +- 22 files changed, 448 insertions(+), 43 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts diff --git a/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.test.ts b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.test.ts index 15750c7667800..6654611faa18b 100644 --- a/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.test.ts +++ b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.test.ts @@ -19,14 +19,51 @@ import { attemptToURIDecode } from './attempt_to_uri_decode'; +// this function doesn't work for % with other special chars or sequence %25 +// known issue https://github.com/elastic/kibana/issues/82440 test('decodes an encoded string', () => { - const encodedString = 'test%3F'; - expect(attemptToURIDecode(encodedString)).toBe('test?'); + const originalName = 'test;,/?:@&=+$#'; + const encodedName = encodeURIComponent(originalName); + // react router v5 automatically decodes route match params + const reactRouterDecoded = decodeURI(encodedName); + + expect(attemptToURIDecode(encodedName)).toBe(originalName); + expect(attemptToURIDecode(reactRouterDecoded)).toBe(originalName); }); -// react router partially decodes %25 sequence to % in match params -// https://github.com/elastic/kibana/pull/81664 test('ignores the error if a string is already decoded', () => { - const decodedString = 'test%'; - expect(attemptToURIDecode(decodedString)).toBe(decodedString); + const originalName = 'test%'; + + const encodedName = encodeURIComponent(originalName); + // react router v5 automatically decodes route match params + const reactRouterDecoded = decodeURI(encodedName); + + expect(attemptToURIDecode(encodedName)).toBe(originalName); + expect(attemptToURIDecode(reactRouterDecoded)).toBe(originalName); +}); + +test('returns wrong decoded value for %25 sequence', () => { + const originalName = 'test%25'; + + const encodedName = encodeURIComponent(originalName); + // react router v5 automatically decodes route match params + const reactRouterDecoded = decodeURI(encodedName); + + expect(attemptToURIDecode(encodedName)).toBe(originalName); + expect(attemptToURIDecode(reactRouterDecoded)).not.toBe(originalName); +}); + +test('returns wrong decoded value for % with other escaped characters', () => { + const originalName = 'test%?#'; + + const encodedName = encodeURIComponent(originalName); + // react router v5 automatically decodes route match params + const reactRouterDecoded = decodeURI(encodedName); + + expect(attemptToURIDecode(encodedName)).toBe(originalName); + expect(attemptToURIDecode(reactRouterDecoded)).not.toBe(originalName); +}); + +test("doesn't convert undefined to a string", () => { + expect(attemptToURIDecode(undefined)).toBeUndefined(); }); diff --git a/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts index 65444b83f77bb..37e4761106e44 100644 --- a/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts +++ b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts @@ -19,12 +19,14 @@ /* * Use this function with any match params coming from react router to safely decode values. - * https://github.com/elastic/kibana/pull/81664 + * After an update to react router v6, this functions should be deprecated. + * Known issue for navigation with special characters in paths + * https://github.com/elastic/kibana/issues/82440 */ -export const attemptToURIDecode = (value: string) => { +export const attemptToURIDecode = (value?: string): string | undefined => { let result = value; try { - result = decodeURIComponent(value); + result = value ? decodeURIComponent(value) : value; } catch (e) { // do nothing } diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx new file mode 100644 index 0000000000000..de7242a6c5ddf --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx @@ -0,0 +1,74 @@ +/* + * 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 React from 'react'; +import { act } from 'react-dom/test-utils'; +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { App } from '../../../public/application/app'; +import { TestSubjects } from '../helpers'; +import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public/context'; + +const breadcrumbService = createBreadcrumbsMock(); + +const AppWithContext = (props: any) => { + return ( + + + + ); +}; + +const getTestBedConfig = (initialEntries: string[]): TestBedConfig => ({ + memoryRouter: { + initialEntries, + }, + defaultProps: { + getUrlForApp: () => {}, + navigateToApp: () => {}, + }, +}); + +const initTestBed = (initialEntries: string[]) => + registerTestBed(AppWithContext, getTestBedConfig(initialEntries))(); + +export interface AppTestBed extends TestBed { + actions: { + clickPolicyNameLink: () => void; + clickCreatePolicyButton: () => void; + }; +} + +export const setup = async (initialEntries: string[]): Promise => { + const testBed = await initTestBed(initialEntries); + + const clickPolicyNameLink = async () => { + const { component, find } = testBed; + await act(async () => { + find('policyTablePolicyNameLink').simulate('click', { button: 0 }); + }); + component.update(); + }; + + const clickCreatePolicyButton = async () => { + const { component, find } = testBed; + await act(async () => { + find('createPolicyButton').simulate('click', { button: 0 }); + }); + component.update(); + }; + + return { + ...testBed, + actions: { clickPolicyNameLink, clickCreatePolicyButton }, + }; +}; + +export const getEncodedPolicyEditPath = (policyName: string): string => + `/policies/edit/${encodeURIComponent(policyName)}`; + +export const getDoubleEncodedPolicyEditPath = (policyName: string): string => + encodeURI(getEncodedPolicyEditPath(policyName)); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts new file mode 100644 index 0000000000000..9052cf2847baa --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts @@ -0,0 +1,252 @@ +/* + * 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 { + AppTestBed, + getDoubleEncodedPolicyEditPath, + getEncodedPolicyEditPath, + setup, +} from './app.helpers'; +import { setupEnvironment } from '../helpers/setup_environment'; +import { getDefaultHotPhasePolicy, POLICY_NAME } from '../edit_policy/constants'; +import { act } from 'react-dom/test-utils'; + +const SPECIAL_CHARS_NAME = 'test?#$+=&@:'; +const PERCENT_SIGN_NAME = 'test%'; +// navigation doesn't work for % with other special chars or sequence %25 +// known issue https://github.com/elastic/kibana/issues/82440 +const PERCENT_SIGN_WITH_OTHER_CHARS_NAME = 'test%#'; +const PERCENT_SIGN_25_SEQUENCE = 'test%25'; + +window.scrollTo = jest.fn(); + +describe('', () => { + let testBed: AppTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + afterAll(() => { + server.restore(); + }); + + describe('new policy creation', () => { + test('when there are no policies', async () => { + httpRequestsMockHelpers.setLoadPolicies([]); + await act(async () => { + testBed = await setup(['/']); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.clickCreatePolicyButton(); + component.update(); + + expect(testBed.find('policyTitle').text()).toBe(`Create an index lifecycle policy`); + expect(testBed.find('policyNameField').props().value).toBe(''); + }); + + test('when there are policies', async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy(POLICY_NAME)]); + await act(async () => { + testBed = await setup(['/']); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.clickCreatePolicyButton(); + component.update(); + + expect(testBed.find('policyTitle').text()).toBe(`Create an index lifecycle policy`); + expect(testBed.find('policyNameField').props().value).toBe(''); + }); + }); + + describe('navigation with special characters', () => { + beforeAll(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy(SPECIAL_CHARS_NAME)]); + }); + + test('clicking policy name in the table works', async () => { + await act(async () => { + testBed = await setup(['/']); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.clickPolicyNameLink(); + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` + ); + }); + + test('loading edit policy page url works', async () => { + await act(async () => { + testBed = await setup([getEncodedPolicyEditPath(SPECIAL_CHARS_NAME)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` + ); + }); + + // using double encoding to counteract react-router's v5 internal decodeURI call + // when those links are open in a new tab, address bar contains double encoded url + test('loading edit policy page url with double encoding works', async () => { + await act(async () => { + testBed = await setup([getDoubleEncodedPolicyEditPath(SPECIAL_CHARS_NAME)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` + ); + }); + }); + + describe('navigation with percent sign', () => { + beforeAll(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy(PERCENT_SIGN_NAME)]); + }); + + test('loading edit policy page url works', async () => { + await act(async () => { + testBed = await setup([getEncodedPolicyEditPath(PERCENT_SIGN_NAME)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_NAME}` + ); + }); + + test('loading edit policy page url with double encoding works', async () => { + await act(async () => { + testBed = await setup([getDoubleEncodedPolicyEditPath(PERCENT_SIGN_NAME)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_NAME}` + ); + }); + }); + + describe('navigation with percent sign with other special characters', () => { + beforeAll(async () => { + httpRequestsMockHelpers.setLoadPolicies([ + getDefaultHotPhasePolicy(PERCENT_SIGN_WITH_OTHER_CHARS_NAME), + ]); + }); + + test('clicking policy name in the table works', async () => { + await act(async () => { + testBed = await setup(['/']); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.clickPolicyNameLink(); + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + ); + }); + + test("loading edit policy page url doesn't work", async () => { + await act(async () => { + testBed = await setup([getEncodedPolicyEditPath(PERCENT_SIGN_WITH_OTHER_CHARS_NAME)]); + }); + + const { component } = testBed; + component.update(); + + // known issue https://github.com/elastic/kibana/issues/82440 + expect(testBed.find('policyTitle').text()).not.toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + ); + }); + + // using double encoding to counteract react-router's v5 internal decodeURI call + // when those links are open in a new tab, address bar contains double encoded url + test('loading edit policy page url with double encoding works', async () => { + await act(async () => { + testBed = await setup([getDoubleEncodedPolicyEditPath(PERCENT_SIGN_WITH_OTHER_CHARS_NAME)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + ); + }); + }); + + describe('navigation with %25 sequence', () => { + beforeAll(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy(PERCENT_SIGN_25_SEQUENCE)]); + }); + + test('clicking policy name in the table works correctly', async () => { + await act(async () => { + testBed = await setup(['/']); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.clickPolicyNameLink(); + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + ); + }); + + test("loading edit policy page url doesn't work", async () => { + await act(async () => { + testBed = await setup([getEncodedPolicyEditPath(PERCENT_SIGN_25_SEQUENCE)]); + }); + + const { component } = testBed; + component.update(); + + // known issue https://github.com/elastic/kibana/issues/82440 + expect(testBed.find('policyTitle').text()).not.toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + ); + }); + + // using double encoding to counteract react-router's v5 internal decodeURI call + // when those links are open in a new tab, address bar contains double encoded url + test('loading edit policy page url with double encoding works', async () => { + await act(async () => { + testBed = await setup([getDoubleEncodedPolicyEditPath(PERCENT_SIGN_25_SEQUENCE)]); + }); + + const { component } = testBed; + component.update(); + + expect(testBed.find('policyTitle').text()).toBe( + `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + ); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts index 3d430cf31621e..00c7d705c1f44 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts @@ -121,6 +121,26 @@ export const DELETE_PHASE_POLICY: PolicyFromES = { name: POLICY_NAME, }; +export const getDefaultHotPhasePolicy = (policyName: string): PolicyFromES => ({ + version: 1, + modified_date: Date.now().toString(), + policy: { + name: policyName, + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_age: '30d', + max_size: '50gb', + }, + }, + }, + }, + }, + name: policyName, +}); + export const POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION: PolicyFromES = { version: 1, modified_date: Date.now().toString(), diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 4ee67d1ed8a19..c91ee3e2a1c06 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -19,6 +19,7 @@ import { POLICY_WITH_INCLUDE_EXCLUDE, POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION, POLICY_WITH_NODE_ROLE_ALLOCATION, + getDefaultHotPhasePolicy, } from './constants'; window.scrollTo = jest.fn(); @@ -33,7 +34,7 @@ describe('', () => { describe('hot phase', () => { describe('serialization', () => { beforeEach(async () => { - httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); httpRequestsMockHelpers.setLoadSnapshotPolicies([]); await act(async () => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts index e8ebc2963d16a..aff9151da61f9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts @@ -17,5 +17,8 @@ export type TestSubjects = | 'hot-selectedMaxDocuments' | 'hot-selectedMaxAge' | 'hot-selectedMaxAgeUnits' + | 'policyTablePolicyNameLink' + | 'policyTitle' + | 'createPolicyButton' | 'freezeSwitch' | string; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index 856981fe5c4f9..20185b02064bc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -15,7 +15,7 @@ import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; import { ROUTES } from './services/navigation'; -export const App = ({ +export const AppWithRouter = ({ history, navigateToApp, getUrlForApp, @@ -23,23 +23,33 @@ export const App = ({ history: ScopedHistory; navigateToApp: ApplicationStart['navigateToApp']; getUrlForApp: ApplicationStart['getUrlForApp']; +}) => ( + + + +); + +export const App = ({ + navigateToApp, + getUrlForApp, +}: { + navigateToApp: ApplicationStart['navigateToApp']; + getUrlForApp: ApplicationStart['getUrlForApp']; }) => { useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []); return ( - - - - } - /> - } - /> - - + + + } + /> + } + /> + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index 3d4cc7dbbd1d4..bb1a4810ba2d2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -12,7 +12,7 @@ import { CloudSetup } from '../../../cloud/public'; import { KibanaContextProvider } from '../shared_imports'; -import { App } from './app'; +import { AppWithRouter } from './app'; import { BreadcrumbService } from './services/breadcrumbs'; @@ -28,7 +28,11 @@ export const renderApp = ( render( - + , element diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx index ebef80871b83d..4c0cc2c8957e1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx @@ -9,7 +9,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../shared_imports'; +import { useKibana, attemptToURIDecode } from '../../../shared_imports'; import { useLoadPoliciesList } from '../../services/api'; import { getPolicyByName } from '../../lib/policies'; @@ -90,13 +90,13 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { verticalPosition="center" horizontalPosition="center" > - +

{isNewPolicy ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index a127574d5bad0..a5844af0bf6dd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -32,6 +32,8 @@ export { TextField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; +export { attemptToURIDecode } from '../../../../src/plugins/es_ui_shared/public'; + export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx index 6d9aa58d6c86b..2fb16874cf943 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx @@ -52,7 +52,7 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent { const { api } = useComponentTemplatesContext(); - const decodedComponentTemplateName = attemptToURIDecode(componentTemplateName); + const decodedComponentTemplateName = attemptToURIDecode(componentTemplateName)!; const { data: componentTemplateDetails, isLoading, error } = api.useLoadComponentTemplate( decodedComponentTemplateName diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index 00ea3ebf794ee..e8424ae46c6d2 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -84,7 +84,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ }), icon: 'pencil', handleActionClick: () => - goToEditComponentTemplate(attemptToURIDecode(componentTemplateName)), + goToEditComponentTemplate(attemptToURIDecode(componentTemplateName)!), }, { name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.cloneActionLabel', { @@ -92,7 +92,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ }), icon: 'copy', handleActionClick: () => - goToCloneComponentTemplate(attemptToURIDecode(componentTemplateName)), + goToCloneComponentTemplate(attemptToURIDecode(componentTemplateName)!), }, { name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.deleteButtonLabel', { @@ -103,7 +103,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ details._kbnMeta.usedBy.length > 0, closePopoverOnClick: true, handleActionClick: () => { - setComponentTemplatesToDelete([attemptToURIDecode(componentTemplateName)]); + setComponentTemplatesToDelete([attemptToURIDecode(componentTemplateName)!]); }, }, ]; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx index 6c03fcf5d9972..e6b403543f4b0 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx @@ -19,7 +19,7 @@ export interface Params { export const ComponentTemplateClone: FunctionComponent> = (props) => { const { sourceComponentTemplateName } = props.match.params; - const decodedSourceName = attemptToURIDecode(sourceComponentTemplateName); + const decodedSourceName = attemptToURIDecode(sourceComponentTemplateName)!; const { toasts, api } = useComponentTemplatesContext(); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx index 934f86f7d7590..500c84a97d222 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx @@ -31,7 +31,7 @@ export const ComponentTemplateEdit: React.FunctionComponent(false); const [saveError, setSaveError] = useState(null); - const decodedName = attemptToURIDecode(name); + const decodedName = attemptToURIDecode(name)!; const { error, data: componentTemplate, isLoading } = api.useLoadComponentTemplate(decodedName); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index ba79319b566bf..20b93d9d71d04 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -231,7 +231,7 @@ export const DataStreamList: React.FunctionComponent { history.push(`/${Section.DataStreams}`); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index f3e82223c30e6..3689a875e28b2 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -101,7 +101,7 @@ export const TemplateList: React.FunctionComponent { - const decodedTemplateName = attemptToURIDecode(name); + const decodedTemplateName = attemptToURIDecode(name)!; const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx index 3e62f7f880f74..e3cb40b3a36e1 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx @@ -27,7 +27,7 @@ export const TemplateEdit: React.FunctionComponent { - const decodedTemplateName = attemptToURIDecode(name); + const decodedTemplateName = attemptToURIDecode(name)!; const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx index 9465117b6b589..df60907973156 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx @@ -25,7 +25,7 @@ export const PipelinesClone: FunctionComponent> const { sourceName } = props.match.params; const { services } = useKibana(); - const decodedSourceName = attemptToURIDecode(sourceName); + const decodedSourceName = attemptToURIDecode(sourceName)!; const { error, data: pipeline, isLoading, isInitialRequest } = services.api.useLoadPipeline( decodedSourceName ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx index 7e2e85ab23fb3..2b53fdb6a6375 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx @@ -38,7 +38,7 @@ export const PipelinesEdit: React.FunctionComponent(false); const [saveError, setSaveError] = useState(null); - const decodedPipelineName = attemptToURIDecode(name); + const decodedPipelineName = attemptToURIDecode(name)!; const { error, data: pipeline, isLoading } = services.api.useLoadPipeline(decodedPipelineName); From 0503f873f8de1790e82f7adfc3bdd758c91f97bd Mon Sep 17 00:00:00 2001 From: Denis Maximov Date: Tue, 10 Nov 2020 12:25:28 +0200 Subject: [PATCH 40/87] [Lens] Do not reset formatting when switching between custom ranges and auto histogram (#82694) --- .../definitions/ranges/ranges.test.tsx | 30 +++++++++++++++++++ .../operations/definitions/ranges/ranges.tsx | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index c8a8ffa7b128f..d43a905434c02 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -744,6 +744,36 @@ describe('ranges', () => { /^Bytes format:/ ); }); + + it('should not reset formatters when switching between custom ranges and auto histogram', () => { + const setStateSpy = jest.fn(); + // now set a format on the range operation + (state.layers.first.columns.col1 as RangeIndexPatternColumn).params.format = { + id: 'custom', + params: { decimals: 3 }, + }; + + const instance = mount( + + ); + + // This series of act closures are made to make it work properly the update flush + act(() => { + instance.find(EuiLink).first().prop('onClick')!({} as ReactMouseEvent); + }); + + expect(setStateSpy.mock.calls[1][0].layers.first.columns.col1.params.format).toEqual({ + id: 'custom', + params: { decimals: 3 }, + }); + }); }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 1050eef45a71c..46d9e4e6c22de 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -225,7 +225,7 @@ export const rangeOperation: OperationDefinition Date: Tue, 10 Nov 2020 13:24:07 +0200 Subject: [PATCH 41/87] [Visualizations] Make the icon buttons labels more descriptive (#82585) * [Visualizations] Make the icon buttons labels more descriptive on the Vis Editor * Fix jest test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/agg.test.tsx.snap | 4 ++-- .../public/components/agg.tsx | 19 +++++++++++++------ .../public/components/agg_add.tsx | 14 +++++++++----- .../translations/translations/ja-JP.json | 5 ----- .../translations/translations/zh-CN.json | 5 ----- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap index 2a521bc01219c..26173cddb3716 100644 --- a/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap +++ b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap @@ -17,12 +17,12 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` extraAction={
{ const actionIcons = []; + const aggTitle = agg.type?.title?.toLowerCase(); if (showError) { actionIcons.push({ @@ -170,7 +172,8 @@ function DefaultEditorAgg({ color: 'danger', type: 'alert', tooltip: i18n.translate('visDefaultEditor.agg.errorsAriaLabel', { - defaultMessage: 'Aggregation has errors', + defaultMessage: '{schemaTitle} {aggTitle} aggregation has errors', + values: { aggTitle, schemaTitle }, }), dataTestSubj: 'hasErrorsAggregationIcon', }); @@ -184,7 +187,8 @@ function DefaultEditorAgg({ type: 'eye', onClick: () => onToggleEnableAgg(agg.id, false), tooltip: i18n.translate('visDefaultEditor.agg.disableAggButtonTooltip', { - defaultMessage: 'Disable aggregation', + defaultMessage: 'Disable {schemaTitle} {aggTitle} aggregation', + values: { aggTitle, schemaTitle }, }), dataTestSubj: 'toggleDisableAggregationBtn disable', }); @@ -196,7 +200,8 @@ function DefaultEditorAgg({ type: 'eyeClosed', onClick: () => onToggleEnableAgg(agg.id, true), tooltip: i18n.translate('visDefaultEditor.agg.enableAggButtonTooltip', { - defaultMessage: 'Enable aggregation', + defaultMessage: 'Enable {schemaTitle} {aggTitle} aggregation', + values: { aggTitle, schemaTitle }, }), dataTestSubj: 'toggleDisableAggregationBtn enable', }); @@ -206,7 +211,8 @@ function DefaultEditorAgg({ id: 'dragHandle', type: 'grab', tooltip: i18n.translate('visDefaultEditor.agg.modifyPriorityButtonTooltip', { - defaultMessage: 'Modify priority by dragging', + defaultMessage: 'Modify priority of {schemaTitle} {aggTitle} by dragging', + values: { aggTitle, schemaTitle }, }), dataTestSubj: 'dragHandleBtn', }); @@ -218,7 +224,8 @@ function DefaultEditorAgg({ type: 'cross', onClick: () => removeAgg(agg.id), tooltip: i18n.translate('visDefaultEditor.agg.removeDimensionButtonTooltip', { - defaultMessage: 'Remove dimension', + defaultMessage: 'Remove {schemaTitle} {aggTitle} aggregation', + values: { aggTitle, schemaTitle }, }), dataTestSubj: 'removeDimensionBtn', }); @@ -257,7 +264,7 @@ function DefaultEditorAgg({
); }; - const schemaTitle = getSchemaByName(schemas, agg.schema).title; + const buttonContent = ( <> {schemaTitle || agg.schema} {showDescription && {aggDescription}} diff --git a/src/plugins/vis_default_editor/public/components/agg_add.tsx b/src/plugins/vis_default_editor/public/components/agg_add.tsx index 46d5af8cec680..e78f2fcc4453c 100644 --- a/src/plugins/vis_default_editor/public/components/agg_add.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_add.tsx @@ -56,22 +56,26 @@ function DefaultEditorAggAdd({ addSchema(schema); }; + const groupNameLabel = + groupName === AggGroupNames.Buckets + ? i18n.translate('visDefaultEditor.aggAdd.bucketLabel', { defaultMessage: 'bucket' }) + : i18n.translate('visDefaultEditor.aggAdd.metricLabel', { defaultMessage: 'metric' }); + const addButton = ( setIsPopoverOpen(!isPopoverOpen)} + aria-label={i18n.translate('visDefaultEditor.aggAdd.addGroupButtonLabel', { + defaultMessage: 'Add {groupNameLabel}', + values: { groupNameLabel }, + })} > ); - const groupNameLabel = - groupName === AggGroupNames.Buckets - ? i18n.translate('visDefaultEditor.aggAdd.bucketLabel', { defaultMessage: 'bucket' }) - : i18n.translate('visDefaultEditor.aggAdd.metricLabel', { defaultMessage: 'metric' }); - const isSchemaDisabled = (schema: Schema): boolean => { const count = group.filter((agg) => agg.schema === schema.name).length; return count >= schema.max; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d8e136567564e..d9a113b69b473 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3542,11 +3542,6 @@ "uiActions.triggers.valueClickTitle": "シングルクリック", "usageCollection.stats.notReadyMessage": "まだ統計が準備できていません。しばらくたってから再試行してください。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高度な設定", - "visDefaultEditor.agg.disableAggButtonTooltip": "集約を無効にする", - "visDefaultEditor.agg.enableAggButtonTooltip": "集約を有効にする", - "visDefaultEditor.agg.errorsAriaLabel": "集約にエラーがあります", - "visDefaultEditor.agg.modifyPriorityButtonTooltip": "ドラッグして優先順位を変更します", - "visDefaultEditor.agg.removeDimensionButtonTooltip": "次元を削除", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", "visDefaultEditor.aggAdd.addButtonLabel": "追加", "visDefaultEditor.aggAdd.addGroupButtonLabel": "{groupNameLabel} を追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 84cc3510ce487..4a393f8a7a223 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3543,11 +3543,6 @@ "uiActions.triggers.valueClickTitle": "单击", "usageCollection.stats.notReadyMessage": "统计信息尚未准备就绪。请稍后重试。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高级", - "visDefaultEditor.agg.disableAggButtonTooltip": "禁用聚合", - "visDefaultEditor.agg.enableAggButtonTooltip": "启用聚合", - "visDefaultEditor.agg.errorsAriaLabel": "聚合有错误", - "visDefaultEditor.agg.modifyPriorityButtonTooltip": "通过拖动来修改优先级", - "visDefaultEditor.agg.removeDimensionButtonTooltip": "移除维度", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "切换 {schema} 编辑器", "visDefaultEditor.aggAdd.addButtonLabel": "添加", "visDefaultEditor.aggAdd.addGroupButtonLabel": "添加{groupNameLabel}", From 471c281fa2572860380d06b26420d694445d1e75 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 10 Nov 2020 13:34:05 +0200 Subject: [PATCH 42/87] [Visualizations] Remove kui usage (#82810) * [Visualize] Remove kui usage * Use EuiPromptButton istead of EuiCallout * Add a link to advanced settings for visualizations docs * Changes requested on code review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/doc_links/doc_links_service.ts | 1 + .../public/embeddable/_index.scss | 1 - .../embeddable/_visualize_lab_disabled.scss | 13 ----- .../embeddable/disabled_lab_visualization.tsx | 51 ++++++++++++------- 4 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 48187fe465392..0815df4b9b0c7 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -131,6 +131,7 @@ export class DocLinksService { management: { kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`, + visualizationSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-visualization-settings`, }, visualize: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`, diff --git a/src/plugins/visualizations/public/embeddable/_index.scss b/src/plugins/visualizations/public/embeddable/_index.scss index c1e3809657bfa..9703e90159f48 100644 --- a/src/plugins/visualizations/public/embeddable/_index.scss +++ b/src/plugins/visualizations/public/embeddable/_index.scss @@ -1,2 +1 @@ -@import 'visualize_lab_disabled'; @import 'embeddables'; diff --git a/src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss b/src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss deleted file mode 100644 index 914480ff8c777..0000000000000 --- a/src/plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss +++ /dev/null @@ -1,13 +0,0 @@ -.visDisabledLabVisualization { - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; -} - -.visDisabledLabVisualization__icon { - font-size: $euiFontSizeXL; -} - diff --git a/src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx b/src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx index 3d2af2c591a3c..ea7760f31d54c 100644 --- a/src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx +++ b/src/plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx @@ -17,29 +17,42 @@ * under the License. */ -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import React from 'react'; +import { getDocLinks } from '../services'; export function DisabledLabVisualization({ title }: { title: string }) { + const advancedSettingsLink = getDocLinks().links.management.visualizationSettings; return ( -
-

+ } + iconType="beaker" + body={ + + } + actions={ + + + + } /> -
- {title} }} - /> -
-
- -
-
+ ); } From a7a83fd4215803d8b835bd4a97a8db4559ef3703 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 10 Nov 2020 13:42:12 +0200 Subject: [PATCH 43/87] [TSVB] Disable using top_hits in pipeline aggregations (#82278) * [TSVB] Disable using top_hits in bucket script aggregations * remove console message * Correct schema for metrics size * Chanhe hardcoded agg with the exported for the METRIC_TYPES var * Exclude top_hit agg from all Sibling Pipeline Aggregations and all Parent Pipeline Aggregations Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/vis_type_timeseries/common/vis_schema.ts | 2 +- .../public/application/components/aggs/calculation.js | 2 ++ .../public/application/components/aggs/cumulative_sum.js | 2 ++ .../public/application/components/aggs/derivative.js | 2 ++ .../public/application/components/aggs/moving_average.js | 2 ++ .../public/application/components/aggs/positive_only.js | 2 ++ .../public/application/components/aggs/serial_diff.js | 2 ++ .../public/application/components/aggs/std_sibling.js | 5 ++++- .../public/application/components/aggs/vars.js | 1 + 9 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 27f09fb574b0f..9ec5ae1424ae3 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -120,7 +120,7 @@ export const metricsItems = schema.object({ type: stringRequired, value: stringOptionalNullable, values: schema.maybe(schema.nullable(schema.arrayOf(schema.nullable(schema.string())))), - size: stringOptionalNullable, + size: stringOrNumberOptionalNullable, agg_with: stringOptionalNullable, order: stringOptionalNullable, order_by: stringOptionalNullable, diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js index bb3d39797656f..5bf4fb55ee5e5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/calculation.js @@ -26,6 +26,7 @@ import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createTextHandler } from '../lib/create_text_handler'; import { CalculationVars, newVariable } from './vars'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -99,6 +100,7 @@ export function CalculationAgg(props) { onChange={handleChange} name="variables" model={model} + exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js index 11b3e303e7e00..0b879adbd37ae 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js @@ -24,6 +24,7 @@ import { AggSelect } from './agg_select'; import { MetricSelect } from './metric_select'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { FormattedMessage } from '@kbn/i18n/react'; import { htmlIdGenerator, @@ -80,6 +81,7 @@ export function CumulativeSumAgg(props) { metrics={siblings} metric={model} value={model.field} + exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js index faf1a59adc4aa..fa1289dc74c72 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js @@ -25,6 +25,7 @@ import { AggRow } from './agg_row'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createTextHandler } from '../lib/create_text_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { htmlIdGenerator, EuiFlexGroup, @@ -91,6 +92,7 @@ export const DerivativeAgg = (props) => { metrics={siblings} metric={model} value={model.field} + exclude={[METRIC_TYPES.TOP_HIT]} fullWidth /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js index 316e0f9af43bd..fb945d2606bc8 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js @@ -25,6 +25,7 @@ import { MetricSelect } from './metric_select'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createNumberHandler } from '../lib/create_number_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { htmlIdGenerator, EuiFlexGroup, @@ -153,6 +154,7 @@ export const MovingAverageAgg = (props) => { metrics={siblings} metric={model} value={model.field} + exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js index 1999862f7aa0e..6ca5fa8e7447f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js @@ -24,6 +24,7 @@ import { MetricSelect } from './metric_select'; import { AggRow } from './agg_row'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { htmlIdGenerator, EuiFlexGroup, @@ -85,6 +86,7 @@ export const PositiveOnlyAgg = (props) => { metrics={siblings} metric={model} value={model.field} + exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js index 10b3d551bb89f..e3a0c74273539 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js @@ -25,6 +25,7 @@ import { AggRow } from './agg_row'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createNumberHandler } from '../lib/create_number_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; import { htmlIdGenerator, EuiFlexGroup, @@ -87,6 +88,7 @@ export const SerialDiffAgg = (props) => { metrics={siblings} metric={model} value={model.field} + exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js index 30e5c57ac90ba..bed5e9caa9f87 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js @@ -25,6 +25,8 @@ import { AggSelect } from './agg_select'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createTextHandler } from '../lib/create_text_handler'; +import { METRIC_TYPES } from '../../../../common/metric_types'; + import { htmlIdGenerator, EuiFlexGroup, @@ -154,7 +156,7 @@ const StandardSiblingAggUi = (props) => { > From 446cffeccf9a19baee96aaf4b146547a3f7f721a Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 10 Nov 2020 07:21:52 -0500 Subject: [PATCH 44/87] renaming built-in alerts to Stack Alerts (#82873) * renaming built-in alerts to Stack Alerts * responding to PR feedback and adding glossary definition for stack alerts * Update docs/glossary.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/glossary.asciidoc | 5 +++++ docs/user/alerting/alert-types.asciidoc | 8 +++++--- docs/user/alerting/alerting-getting-started.asciidoc | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/glossary.asciidoc b/docs/glossary.asciidoc index be24402170bbe..ff03a60173961 100644 --- a/docs/glossary.asciidoc +++ b/docs/glossary.asciidoc @@ -330,6 +330,11 @@ See {kibana-ref}/xpack-spaces.html[Spaces]. // end::space-def[] +[[glossary-stack-alerts]] stack alerts :: +// tag::stack-alert-def[] +The general purpose alert types {kib} provides out of the box. Index threshold and geo alerts are currently the two stack alert types. +// end::stack-alert-def[] + [float] [[t_glos]] diff --git a/docs/user/alerting/alert-types.asciidoc b/docs/user/alerting/alert-types.asciidoc index f71e43c5defc7..7de5ff56228cc 100644 --- a/docs/user/alerting/alert-types.asciidoc +++ b/docs/user/alerting/alert-types.asciidoc @@ -2,11 +2,13 @@ [[alert-types]] == Alert types -{kib} supplies alerts types in two ways: some are built into {kib}, while domain-specific alert types are registered by {kib} apps such as <>, <>, and <>. +{kib} supplies alert types in two ways: some are built into {kib} (these are known as stack alerts), while domain-specific alert types are registered by {kib} apps such as <>, <>, and <>. -This section covers built-in alert types. For domain-specific alert types, refer to the documentation for that app. +This section covers stack alerts. For domain-specific alert types, refer to the documentation for that app. +Users will need `all` access to the *Stack Alerts* feature to be able to create and edit any of the alerts listed below. +See <> for more information on configuring roles that provide access to this feature. -Currently {kib} provides one built-in alert type: the <> type. +Currently {kib} provides one stack alert: the <> type. [float] [[alert-type-index-threshold]] diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index 2b22b49375676..53aef4aaa062e 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -6,7 +6,7 @@ beta[] -- -Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> for you to use. +Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> (known as stack alerts) for you to use. image::images/alerting-overview.png[Alerts and actions UI] From 451e387f40d211d1eb36589c7d8de95d4e6e94b6 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 10 Nov 2020 07:37:44 -0500 Subject: [PATCH 45/87] Fix SO query for searching across spaces (#83025) --- .../service/lib/search_dsl/query_params.test.ts | 5 ++--- .../service/lib/search_dsl/query_params.ts | 14 ++++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index c35ec809fcf8d..e78b944183df9 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -103,12 +103,11 @@ describe('#getQueryParams', () => { if (registry.isMultiNamespace(type)) { const array = [...(namespaces ?? [DEFAULT_NAMESPACE_STRING]), ALL_NAMESPACES_STRING]; - const namespacesClause = { terms: { namespaces: array } }; return { bool: { must: namespaces?.includes(ALL_NAMESPACES_STRING) - ? expect.not.arrayContaining([namespacesClause]) - : expect.arrayContaining([namespacesClause]), + ? [{ term: { type } }] + : [{ term: { type } }, { terms: { namespaces: array } }], must_not: [{ exists: { field: 'namespace' } }], }, }; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 2ecba42e408e7..cb58db171681a 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -78,13 +78,19 @@ function getClauseForType( const searchAcrossAllNamespaces = namespaces.includes(ALL_NAMESPACES_STRING); if (registry.isMultiNamespace(type)) { - const namespacesFilterClause = searchAcrossAllNamespaces - ? {} - : { terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] } }; + const typeFilterClause = { term: { type } }; + + const namespacesFilterClause = { + terms: { namespaces: [...namespaces, ALL_NAMESPACES_STRING] }, + }; + + const must = searchAcrossAllNamespaces + ? [typeFilterClause] + : [typeFilterClause, namespacesFilterClause]; return { bool: { - must: [{ term: { type } }, namespacesFilterClause], + must, must_not: [{ exists: { field: 'namespace' } }], }, }; From 6003cadce4abfa19dfec1d08b037d45ba11367d7 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 10 Nov 2020 12:43:39 +0000 Subject: [PATCH 46/87] skip flaky suite (#82804) --- .../security_and_spaces/tests/alerting/update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 0ad2ca226ed5d..8836bc2e4db2f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -31,7 +31,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .then((response: SupertestResponse) => response.body); } - describe('update', () => { + // FLAKY: https://github.com/elastic/kibana/issues/82804 + describe.skip('update', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); From 0b99841310b804901827040aaf025468941710a3 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 10 Nov 2020 14:31:04 +0100 Subject: [PATCH 47/87] [Lens] Performance refactoring for indexpattern fast lookup and Operation support matrix computation (#82829) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../datapanel.test.tsx | 254 +++++++------- .../indexpattern_datasource/datapanel.tsx | 5 +- .../bucket_nesting_editor.test.tsx | 22 +- .../dimension_panel/bucket_nesting_editor.tsx | 15 +- .../dimension_panel/dimension_editor.tsx | 45 ++- .../dimension_panel/dimension_panel.test.tsx | 74 ++-- .../dimension_panel/droppable.test.ts | 91 +++-- .../dimension_panel/droppable.ts | 6 +- .../dimension_panel/field_select.tsx | 25 +- .../dimension_panel/operation_support.ts | 38 +- .../indexpattern.test.ts | 205 +++++------ .../indexpattern_suggestions.test.tsx | 326 ++++++++++-------- .../indexpattern_suggestions.ts | 32 +- .../layerpanel.test.tsx | 232 +++++++------ .../indexpattern_datasource/loader.test.ts | 13 +- .../public/indexpattern_datasource/loader.ts | 2 + .../public/indexpattern_datasource/mocks.ts | 117 ++++--- .../operations/definitions/cardinality.tsx | 2 +- .../definitions/date_histogram.test.tsx | 79 ++++- .../operations/definitions/date_histogram.tsx | 19 +- .../operations/definitions/metrics.tsx | 2 +- .../definitions/ranges/ranges.test.tsx | 4 + .../operations/definitions/ranges/ranges.tsx | 6 +- .../operations/definitions/terms/index.tsx | 2 +- .../definitions/terms/terms.test.tsx | 2 +- .../operations/operations.test.ts | 50 +-- .../indexpattern_datasource/pure_helpers.ts | 8 +- .../state_helpers.test.ts | 101 +++--- .../public/indexpattern_datasource/types.ts | 1 + .../public/indexpattern_datasource/utils.ts | 10 +- 30 files changed, 996 insertions(+), 792 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index c48bc3dc52404..d2ec1c81bbeec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -18,6 +18,131 @@ import { ChangeIndexPattern } from './change_indexpattern'; import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui'; import { documentField } from './document_field'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; +import { getFieldByNameFactory } from './pure_helpers'; + +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'amemory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'unsupported', + displayName: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'client', + displayName: 'client', + type: 'ip', + aggregatable: true, + searchable: true, + }, + documentField, +]; + +const fieldsTwo = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', + }, + }, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + histogram: { + agg: 'histogram', + interval: 1000, + }, + max: { + agg: 'max', + }, + min: { + agg: 'min', + }, + sum: { + agg: 'sum', + }, + }, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + terms: { + agg: 'terms', + }, + }, + }, + documentField, +]; + +const fieldsThree = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + documentField, +]; const initialState: IndexPatternPrivateState = { indexPatternRefs: [], @@ -85,139 +210,24 @@ const initialState: IndexPatternPrivateState = { title: 'idx1', timeFieldName: 'timestamp', hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'memory', - displayName: 'amemory', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'unsupported', - displayName: 'unsupported', - type: 'geo', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - { - name: 'client', - displayName: 'client', - type: 'ip', - aggregatable: true, - searchable: true, - }, - documentField, - ], + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), }, '2': { id: '2', title: 'idx2', timeFieldName: 'timestamp', hasRestrictions: true, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', - }, - }, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - histogram: { - agg: 'histogram', - interval: 1000, - }, - max: { - agg: 'max', - }, - min: { - agg: 'min', - }, - sum: { - agg: 'sum', - }, - }, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - terms: { - agg: 'terms', - }, - }, - }, - documentField, - ], + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), }, '3': { id: '3', title: 'idx3', timeFieldName: 'timestamp', hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - documentField, - ], + fields: fieldsThree, + getFieldByName: getFieldByNameFactory(fieldsThree), }, }, isFirstExistenceFetch: false, @@ -330,6 +340,7 @@ describe('IndexPattern Data Panel', () => { title: 'aaa', timeFieldName: 'atime', fields: [], + getFieldByName: getFieldByNameFactory([]), hasRestrictions: false, }, b: { @@ -337,6 +348,7 @@ describe('IndexPattern Data Panel', () => { title: 'bbb', timeFieldName: 'btime', fields: [], + getFieldByName: getFieldByNameFactory([]), hasRestrictions: false, }, }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 28c5605f3bfc5..f2c7d7fc20926 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -5,7 +5,7 @@ */ import './datapanel.scss'; -import { uniq, keyBy, groupBy } from 'lodash'; +import { uniq, groupBy } from 'lodash'; import React, { useState, memo, useCallback, useMemo } from 'react'; import { EuiFlexGroup, @@ -266,9 +266,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ const fieldInfoUnavailable = existenceFetchFailed || currentIndexPattern.hasRestrictions; const unfilteredFieldGroups: FieldGroups = useMemo(() => { - const fieldByName = keyBy(allFields, 'name'); const containsData = (field: IndexPatternField) => { - const overallField = fieldByName[field.name]; + const overallField = currentIndexPattern.getFieldByName(field.name); return ( overallField && fieldExists(existingFields, currentIndexPattern.title, overallField.name) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx index 3696f3ad7b102..ee6a86072236c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx @@ -10,12 +10,14 @@ import { BucketNestingEditor } from './bucket_nesting_editor'; import { IndexPatternColumn } from '../indexpattern'; import { IndexPatternField } from '../types'; -const fieldMap = { +const fieldMap: Record = { a: { displayName: 'a' } as IndexPatternField, b: { displayName: 'b' } as IndexPatternField, c: { displayName: 'c' } as IndexPatternField, }; +const getFieldByName = (name: string): IndexPatternField | undefined => fieldMap[name]; + describe('BucketNestingEditor', () => { function mockCol(col: Partial = {}): IndexPatternColumn { const result = { @@ -39,7 +41,7 @@ describe('BucketNestingEditor', () => { it('should display the top level grouping when at the root', () => { const component = mount( { const component = mount( { const component = mount( { const component = mount( { const component = mount( { const component = mount( { const component = mount( { const component = mount( { const setColumns = jest.fn(); const component = mount( , column: IndexPatternColumn) { - return hasField(column) ? fieldMap[column.sourceField]?.displayName || column.sourceField : ''; +function getFieldName( + column: IndexPatternColumn, + getFieldByName: (name: string) => IndexPatternField | undefined +) { + return hasField(column) + ? getFieldByName(column.sourceField)?.displayName || column.sourceField + : ''; } export function BucketNestingEditor({ columnId, layer, setColumns, - fieldMap, + getFieldByName, }: { columnId: string; layer: IndexPatternLayer; setColumns: (columns: string[]) => void; - fieldMap: Record; + getFieldByName: (name: string) => IndexPatternField | undefined; }) { const column = layer.columns[columnId]; const columns = Object.entries(layer.columns); @@ -42,7 +47,7 @@ export function BucketNestingEditor({ .map(([value, c]) => ({ value, text: c.label, - fieldName: getFieldName(fieldMap, c), + fieldName: getFieldName(c, getFieldByName), operationType: c.operationType, })); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index a18cb69db74cb..7cbfbc1749382 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -29,7 +29,7 @@ import { deleteColumn, changeColumn, updateColumnParam, mergeLayer } from '../st import { FieldSelect } from './field_select'; import { hasField, fieldIsInvalid } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; -import { IndexPattern, IndexPatternField } from '../types'; +import { IndexPattern } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; @@ -97,23 +97,13 @@ export function DimensionEditor(props: DimensionEditorProps) { const ParamEditor = selectedOperationDefinition?.paramEditor; - const fieldMap: Record = useMemo(() => { - const fields: Record = {}; - currentIndexPattern.fields.forEach((field) => { - fields[field.name] = field; - }); - return fields; - }, [currentIndexPattern]); - const possibleOperations = useMemo(() => { return Object.values(operationDefinitionMap) .sort((op1, op2) => { return op1.displayName.localeCompare(op2.displayName); }) .map((def) => def.type) - .filter( - (type) => fieldByOperation[type]?.length || operationWithoutField.indexOf(type) !== -1 - ); + .filter((type) => fieldByOperation[type]?.size || operationWithoutField.has(type)); }, [fieldByOperation, operationWithoutField]); // Operations are compatible if they match inputs. They are always compatible in @@ -128,7 +118,7 @@ export function DimensionEditor(props: DimensionEditorProps) { (selectedColumn && hasField(selectedColumn) && definition.input === 'field' && - fieldByOperation[operationType]?.indexOf(selectedColumn.sourceField) !== -1) || + fieldByOperation[operationType]?.has(selectedColumn.sourceField)) || (selectedColumn && !hasField(selectedColumn) && definition.input !== 'field'), }; }); @@ -198,9 +188,9 @@ export function DimensionEditor(props: DimensionEditorProps) { trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } else if (!selectedColumn || !compatibleWithCurrentField) { - const possibleFields = fieldByOperation[operationType] || []; + const possibleFields = fieldByOperation[operationType] || new Set(); - if (possibleFields.length === 1) { + if (possibleFields.size === 1) { setState( changeColumn({ state, @@ -212,7 +202,7 @@ export function DimensionEditor(props: DimensionEditorProps) { layerId: props.layerId, op: operationType, indexPattern: currentIndexPattern, - field: fieldMap[possibleFields[0]], + field: currentIndexPattern.getFieldByName(possibleFields.values().next().value), previousColumn: selectedColumn, }), }) @@ -236,7 +226,9 @@ export function DimensionEditor(props: DimensionEditorProps) { layerId: props.layerId, op: operationType, indexPattern: currentIndexPattern, - field: hasField(selectedColumn) ? fieldMap[selectedColumn.sourceField] : undefined, + field: hasField(selectedColumn) + ? currentIndexPattern.getFieldByName(selectedColumn.sourceField) + : undefined, previousColumn: selectedColumn, }); @@ -297,7 +289,6 @@ export function DimensionEditor(props: DimensionEditorProps) { fieldIsInvalid={currentFieldIsInvalid} currentIndexPattern={currentIndexPattern} existingFields={state.existingFields} - fieldMap={fieldMap} operationSupportMatrix={operationSupportMatrix} selectedColumnOperationType={selectedColumn && selectedColumn.operationType} selectedColumnSourceField={ @@ -323,25 +314,29 @@ export function DimensionEditor(props: DimensionEditorProps) { ) { // If we just changed the field are not in an error state and the operation didn't change, // we use the operations onFieldChange method to calculate the new column. - column = changeField(selectedColumn, currentIndexPattern, fieldMap[choice.field]); + column = changeField( + selectedColumn, + currentIndexPattern, + currentIndexPattern.getFieldByName(choice.field)! + ); } else { // Otherwise we'll use the buildColumn method to calculate a new column const compatibleOperations = ('field' in choice && operationSupportMatrix.operationByField[choice.field]) || - []; + new Set(); let operation; - if (compatibleOperations.length > 0) { + if (compatibleOperations.size > 0) { operation = incompatibleSelectedOperationType && - compatibleOperations.includes(incompatibleSelectedOperationType) + compatibleOperations.has(incompatibleSelectedOperationType) ? incompatibleSelectedOperationType - : compatibleOperations[0]; + : compatibleOperations.values().next().value; } else if ('field' in choice) { operation = choice.operationType; } column = buildColumn({ columns: props.state.layers[props.layerId].columns, - field: fieldMap[choice.field], + field: currentIndexPattern.getFieldByName(choice.field), indexPattern: currentIndexPattern, layerId: props.layerId, suggestedPriority: props.suggestedPriority, @@ -417,12 +412,12 @@ export function DimensionEditor(props: DimensionEditorProps) { {!incompatibleSelectedOperationType && !hideGrouping && ( setState(mergeLayer({ state, layerId, newLayer: { columnOrder } })) } + getFieldByName={currentIndexPattern.getFieldByName} /> )} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 92a4dad14dd25..3ed04b08df58f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -22,6 +22,7 @@ import { IndexPatternColumn } from '../operations'; import { documentField } from '../document_field'; import { OperationMetadata } from '../../types'; import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram'; +import { getFieldByNameFactory } from '../pure_helpers'; jest.mock('../loader'); jest.mock('../state_helpers'); @@ -34,6 +35,42 @@ jest.mock('lodash', () => { }; }); +const fields = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + exists: true, + }, + documentField, +]; + const expectedIndexPatterns = { 1: { id: '1', @@ -41,41 +78,8 @@ const expectedIndexPatterns = { timeFieldName: 'timestamp', hasExistence: true, hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - exists: true, - }, - documentField, - ], + fields, + getFieldByName: getFieldByNameFactory(fields), }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index dd696f8be357f..1d85c1f8f78ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -15,9 +15,46 @@ import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; import { OperationMetadata } from '../../types'; import { IndexPatternColumn } from '../operations'; +import { getFieldByNameFactory } from '../pure_helpers'; jest.mock('../state_helpers'); +const fields = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + exists: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + exists: true, + }, + documentField, +]; + const expectedIndexPatterns = { 1: { id: '1', @@ -25,41 +62,8 @@ const expectedIndexPatterns = { timeFieldName: 'timestamp', hasExistence: true, hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - exists: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - exists: true, - }, - documentField, - ], + fields, + getFieldByName: getFieldByNameFactory(fields), }, }; @@ -177,6 +181,23 @@ describe('IndexPatternDimensionEditorPanel', () => { type: 'string', }, ], + + getFieldByName: getFieldByNameFactory([ + { + aggregatable: true, + name: 'bar', + displayName: 'bar', + searchable: true, + type: 'number', + }, + { + aggregatable: true, + name: 'mystring', + displayName: 'mystring', + searchable: true, + type: 'string', + }, + ]), }, }, currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index 7f509cd0244f0..a6ff550af96e9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -137,9 +137,9 @@ export function onDrop(props: DatasourceDimensionDropHandlerProps { currentIndexPattern: IndexPattern; - fieldMap: Record; incompatibleSelectedOperationType: OperationType | null; selectedColumnOperationType?: OperationType; selectedColumnSourceField?: string; @@ -46,7 +45,6 @@ export interface FieldSelectProps extends EuiComboBoxProps<{}> { export function FieldSelect({ currentIndexPattern, - fieldMap, incompatibleSelectedOperationType, selectedColumnOperationType, selectedColumnSourceField, @@ -63,31 +61,32 @@ export function FieldSelect({ function isCompatibleWithCurrentOperation(fieldName: string) { if (incompatibleSelectedOperationType) { - return operationByField[fieldName]!.includes(incompatibleSelectedOperationType); + return operationByField[fieldName]!.has(incompatibleSelectedOperationType); } return ( !selectedColumnOperationType || - operationByField[fieldName]!.includes(selectedColumnOperationType) + operationByField[fieldName]!.has(selectedColumnOperationType) ); } const [specialFields, normalFields] = _.partition( fields, - (field) => fieldMap[field].type === 'document' + (field) => currentIndexPattern.getFieldByName(field)?.type === 'document' ); const containsData = (field: string) => - fieldMap[field].type === 'document' || + currentIndexPattern.getFieldByName(field)?.type === 'document' || fieldExists(existingFields, currentIndexPattern.title, field); function fieldNamesToOptions(items: string[]) { return items + .filter((field) => currentIndexPattern.getFieldByName(field)?.displayName) .map((field) => ({ - label: fieldMap[field].displayName, + label: currentIndexPattern.getFieldByName(field)?.displayName, value: { type: 'field', field, - dataType: fieldMap[field].type, + dataType: currentIndexPattern.getFieldByName(field)?.type, operationType: selectedColumnOperationType && isCompatibleWithCurrentOperation(field) ? selectedColumnOperationType @@ -118,7 +117,10 @@ export function FieldSelect({ })); } - const [metaFields, nonMetaFields] = _.partition(normalFields, (field) => fieldMap[field].meta); + const [metaFields, nonMetaFields] = _.partition( + normalFields, + (field) => currentIndexPattern.getFieldByName(field)?.meta + ); const [availableFields, emptyFields] = _.partition(nonMetaFields, containsData); const constructFieldsOptions = (fieldsArr: string[], label: string) => @@ -158,7 +160,6 @@ export function FieldSelect({ incompatibleSelectedOperationType, selectedColumnOperationType, currentIndexPattern, - fieldMap, operationByField, existingFields, ]); @@ -180,7 +181,7 @@ export function FieldSelect({ { label: fieldIsInvalid ? selectedColumnSourceField - : fieldMap[selectedColumnSourceField]?.displayName, + : currentIndexPattern.getFieldByName(selectedColumnSourceField)?.displayName, value: { type: 'field', field: selectedColumnSourceField }, }, ] diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts index 2ea28da201556..31fb5277d53ec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/operation_support.ts @@ -11,9 +11,9 @@ import { getAvailableOperationsByMetadata } from '../operations'; import { IndexPatternPrivateState } from '../types'; export interface OperationSupportMatrix { - operationByField: Partial>; - operationWithoutField: OperationType[]; - fieldByOperation: Partial>; + operationByField: Partial>>; + operationWithoutField: Set; + fieldByOperation: Partial>>; } type Props = Pick< @@ -31,30 +31,30 @@ export const getOperationSupportMatrix = (props: Props): OperationSupportMatrix currentIndexPattern ).filter((operation) => props.filterOperations(operation.operationMetaData)); - const supportedOperationsByField: Partial> = {}; - const supportedOperationsWithoutField: OperationType[] = []; - const supportedFieldsByOperation: Partial> = {}; + const supportedOperationsByField: Partial>> = {}; + const supportedOperationsWithoutField: Set = new Set(); + const supportedFieldsByOperation: Partial>> = {}; filteredOperationsByMetadata.forEach(({ operations }) => { operations.forEach((operation) => { if (operation.type === 'field') { - supportedOperationsByField[operation.field] = [ - ...(supportedOperationsByField[operation.field] ?? []), - operation.operationType, - ]; - - supportedFieldsByOperation[operation.operationType] = [ - ...(supportedFieldsByOperation[operation.operationType] ?? []), - operation.field, - ]; + if (!supportedOperationsByField[operation.field]) { + supportedOperationsByField[operation.field] = new Set(); + } + supportedOperationsByField[operation.field]?.add(operation.operationType); + + if (!supportedFieldsByOperation[operation.operationType]) { + supportedFieldsByOperation[operation.operationType] = new Set(); + } + supportedFieldsByOperation[operation.operationType]?.add(operation.field); } else if (operation.type === 'none') { - supportedOperationsWithoutField.push(operation.operationType); + supportedOperationsWithoutField.add(operation.operationType); } }); }); return { - operationByField: _.mapValues(supportedOperationsByField, _.uniq), - operationWithoutField: _.uniq(supportedOperationsWithoutField), - fieldByOperation: _.mapValues(supportedFieldsByOperation, _.uniq), + operationByField: supportedOperationsByField, + operationWithoutField: supportedOperationsWithoutField, + fieldByOperation: supportedFieldsByOperation, }; }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index a3f48b162475a..51d95245adb25 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -12,121 +12,128 @@ import { IndexPatternPersistedState, IndexPatternPrivateState } from './types'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { Ast } from '@kbn/interpreter/common'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; +import { getFieldByNameFactory } from './pure_helpers'; jest.mock('./loader'); jest.mock('../id_generator'); -const expectedIndexPatterns = { - 1: { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + +const fieldsTwo = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', }, - { - name: 'start_date', - displayName: 'start_date', - type: 'date', - aggregatable: true, - searchable: true, + }, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + // Ignored in the UI + histogram: { + agg: 'histogram', + interval: 1000, }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, + avg: { + agg: 'avg', }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, + max: { + agg: 'max', }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, + min: { + agg: 'min', }, - { - name: 'dest', - displayName: 'dest', - type: 'string', - aggregatable: true, - searchable: true, + sum: { + agg: 'sum', }, - ], + }, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + terms: { + agg: 'terms', + }, + }, + }, +]; + +const expectedIndexPatterns = { + 1: { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), }, 2: { id: '2', title: 'my-fake-restricted-pattern', timeFieldName: 'timestamp', hasRestrictions: true, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', - }, - }, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - // Ignored in the UI - histogram: { - agg: 'histogram', - interval: 1000, - }, - avg: { - agg: 'avg', - }, - max: { - agg: 'max', - }, - min: { - agg: 'min', - }, - sum: { - agg: 'sum', - }, - }, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - terms: { - agg: 'terms', - }, - }, - }, - ], + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index c8cb9fcb33ba9..523a1be34ba3d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -12,121 +12,128 @@ import { getDatasourceSuggestionsFromCurrentState, getDatasourceSuggestionsForVisualizeField, } from './indexpattern_suggestions'; +import { getFieldByNameFactory } from './pure_helpers'; jest.mock('./loader'); jest.mock('../id_generator'); -const expectedIndexPatterns = { - 1: { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + +const fieldsTwo = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', }, - { - name: 'start_date', - displayName: 'start_date', - type: 'date', - aggregatable: true, - searchable: true, + }, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + // Ignored in the UI + histogram: { + agg: 'histogram', + interval: 1000, }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, + avg: { + agg: 'avg', }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, + max: { + agg: 'max', }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, + min: { + agg: 'min', }, - { - name: 'dest', - displayName: 'dest', - type: 'string', - aggregatable: true, - searchable: true, + sum: { + agg: 'sum', + }, + }, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + terms: { + agg: 'terms', }, - ], + }, + }, +]; + +const expectedIndexPatterns = { + 1: { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), }, 2: { id: '2', title: 'my-fake-restricted-pattern', hasRestrictions: true, timeFieldName: 'timestamp', - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', - }, - }, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - // Ignored in the UI - histogram: { - agg: 'histogram', - interval: 1000, - }, - avg: { - agg: 'avg', - }, - max: { - agg: 'max', - }, - min: { - agg: 'min', - }, - sum: { - agg: 'sum', - }, - }, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - terms: { - agg: 'terms', - }, - }, - }, - ], + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), }, }; @@ -335,6 +342,15 @@ describe('IndexPattern Data Source suggestions', () => { searchable: true, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), }, }, layers: { @@ -546,6 +562,16 @@ describe('IndexPattern Data Source suggestions', () => { searchable: true, }, ], + + getFieldByName: getFieldByNameFactory([ + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), }, }, layers: { @@ -1531,6 +1557,43 @@ describe('IndexPattern Data Source suggestions', () => { it('returns simplified versions of table with more than 2 columns', () => { const initialState = testInitialState(); + const fields = [ + { + name: 'field1', + displayName: 'field1', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'field2', + displayName: 'field2', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'field3', + displayName: 'field3Label', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'field4', + displayName: 'field4', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'field5', + displayName: 'field5', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]; const state: IndexPatternPrivateState = { indexPatternRefs: [], existingFields: {}, @@ -1540,43 +1603,8 @@ describe('IndexPattern Data Source suggestions', () => { id: '1', title: 'my-fake-index-pattern', hasRestrictions: false, - fields: [ - { - name: 'field1', - displayName: 'field1', - type: 'string', - aggregatable: true, - searchable: true, - }, - { - name: 'field2', - displayName: 'field2', - type: 'string', - aggregatable: true, - searchable: true, - }, - { - name: 'field3', - displayName: 'field3Label', - type: 'string', - aggregatable: true, - searchable: true, - }, - { - name: 'field4', - displayName: 'field4', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'field5', - displayName: 'field5', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], + fields, + getFieldByName: getFieldByNameFactory(fields), }, }, isFirstExistenceFetch: false, @@ -1700,6 +1728,23 @@ describe('IndexPattern Data Source suggestions', () => { searchable: true, }, ], + + getFieldByName: getFieldByNameFactory([ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'field2', + displayName: 'field2', + type: 'date', + aggregatable: true, + searchable: true, + }, + ]), }, }, isFirstExistenceFetch: false, @@ -1756,6 +1801,15 @@ describe('IndexPattern Data Source suggestions', () => { searchable: true, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'field1', + displayName: 'field1', + type: 'number', + aggregatable: true, + searchable: true, + }, + ]), }, }, isFirstExistenceFetch: false, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 098569d1f460a..c12d7d4be226b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -128,7 +128,7 @@ export function getDatasourceSuggestionsForVisualizeField( const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); // Identify the field by the indexPatternId and the fieldName const indexPattern = state.indexPatterns[indexPatternId]; - const field = indexPattern.fields.find((fld) => fld.name === fieldName); + const field = indexPattern.getFieldByName(fieldName); if (layerIds.length !== 0 || !field) return []; const newId = generateId(); @@ -371,7 +371,7 @@ function createNewLayerWithMetricAggregation( indexPattern: IndexPattern, field: IndexPatternField ): IndexPatternLayer { - const dateField = indexPattern.fields.find((f) => f.name === indexPattern.timeFieldName)!; + const dateField = indexPattern.getFieldByName(indexPattern.timeFieldName!); const column = getMetricColumn(indexPattern, layerId, field); @@ -451,9 +451,8 @@ export function getDatasourceSuggestionsFromCurrentState( (columnId) => layer.columns[columnId].isBucketed && layer.columns[columnId].dataType === 'date' ); - const timeField = indexPattern.fields.find( - ({ name }) => name === indexPattern.timeFieldName - ); + const timeField = + indexPattern.timeFieldName && indexPattern.getFieldByName(indexPattern.timeFieldName); const hasNumericDimension = buckets.length === 1 && @@ -507,17 +506,17 @@ function createChangedNestingSuggestion(state: IndexPatternPrivateState, layerId const layer = state.layers[layerId]; const [firstBucket, secondBucket, ...rest] = layer.columnOrder; const updatedLayer = { ...layer, columnOrder: [secondBucket, firstBucket, ...rest] }; - const currentFields = state.indexPatterns[state.currentIndexPatternId].fields; + const indexPattern = state.indexPatterns[state.currentIndexPatternId]; + const firstBucketColumn = layer.columns[firstBucket]; const firstBucketLabel = - currentFields.find((field) => { - const column = layer.columns[firstBucket]; - return hasField(column) && column.sourceField === field.name; - })?.displayName || ''; + (hasField(firstBucketColumn) && + indexPattern.getFieldByName(firstBucketColumn.sourceField)?.displayName) || + ''; + const secondBucketColumn = layer.columns[secondBucket]; const secondBucketLabel = - currentFields.find((field) => { - const column = layer.columns[secondBucket]; - return hasField(column) && column.sourceField === field.name; - })?.displayName || ''; + (hasField(secondBucketColumn) && + indexPattern.getFieldByName(secondBucketColumn.sourceField)?.displayName) || + ''; return buildSuggestion({ state, @@ -604,7 +603,10 @@ function createAlternativeMetricSuggestions( if (!hasField(column)) { return; } - const field = indexPattern.fields.find(({ name }) => column.sourceField === name)!; + const field = indexPattern.getFieldByName(column.sourceField); + if (!field) { + return; + } const alternativeMetricOperations = getOperationTypesForField(field) .map((op) => buildColumn({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 92e35b257f24a..40eb52fe67c6d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -11,6 +11,7 @@ import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; import { ShallowWrapper } from 'enzyme'; import { EuiSelectable } from '@elastic/eui'; import { ChangeIndexPattern } from './change_indexpattern'; +import { getFieldByNameFactory } from './pure_helpers'; jest.mock('./state_helpers'); @@ -19,6 +20,120 @@ interface IndexPatternPickerOption { checked?: 'on' | 'off'; } +const fieldsOne = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'unsupported', + displayName: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + +const fieldsTwo = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', + }, + }, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + histogram: { + agg: 'histogram', + interval: 1000, + }, + max: { + agg: 'max', + }, + min: { + agg: 'min', + }, + sum: { + agg: 'sum', + }, + }, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + terms: { + agg: 'terms', + }, + }, + }, +]; + +const fieldsThree = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + const initialState: IndexPatternPrivateState = { indexPatternRefs: [ { id: '1', title: 'my-fake-index-pattern' }, @@ -63,129 +178,24 @@ const initialState: IndexPatternPrivateState = { title: 'my-fake-index-pattern', timeFieldName: 'timestamp', hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'unsupported', - displayName: 'unsupported', - type: 'geo', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - ], + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), }, '2': { id: '2', title: 'my-fake-restricted-pattern', hasRestrictions: true, timeFieldName: 'timestamp', - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', - }, - }, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - histogram: { - agg: 'histogram', - interval: 1000, - }, - max: { - agg: 'max', - }, - min: { - agg: 'min', - }, - sum: { - agg: 'sum', - }, - }, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - terms: { - agg: 'terms', - }, - }, - }, - ], + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), }, '3': { id: '3', title: 'my-compatible-pattern', timeFieldName: 'timestamp', hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - ], + fields: fieldsThree, + getFieldByName: getFieldByNameFactory(fieldsThree), }, }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 4222c02388433..adb86253ab28c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -285,15 +285,10 @@ describe('loader', () => { } as unknown) as Pick, }); - expect( - cache.foo.fields.find((f: IndexPatternField) => f.name === 'bytes')!.aggregationRestrictions - ).toEqual({ + expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({ sum: { agg: 'sum' }, }); - expect( - cache.foo.fields.find((f: IndexPatternField) => f.name === 'timestamp')! - .aggregationRestrictions - ).toEqual({ + expect(cache.foo.getFieldByName('timestamp')!.aggregationRestrictions).toEqual({ date_histogram: { agg: 'date_histogram', fixed_interval: 'm' }, }); }); @@ -342,9 +337,7 @@ describe('loader', () => { } as unknown) as Pick, }); - expect(cache.foo.fields.find((f: IndexPatternField) => f.name === 'timestamp')!.meta).toEqual( - true - ); + expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 70079cce6cc46..fac5d7350e45e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -26,6 +26,7 @@ import { import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; import { documentField } from './document_field'; import { readFromStorage, writeToStorage } from '../settings_storage'; +import { getFieldByNameFactory } from './pure_helpers'; type SetState = StateSetter; type SavedObjectsClient = Pick; @@ -112,6 +113,7 @@ export async function loadIndexPatterns({ ]) ), fields: newFields, + getFieldByName: getFieldByNameFactory(newFields), hasRestrictions: !!typeMeta?.aggs, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts index 744a9f6743d09..2c6f42668d863 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts @@ -5,14 +5,11 @@ */ import { DragContextState } from '../drag_drop'; +import { getFieldByNameFactory } from './pure_helpers'; import { IndexPattern } from './types'; -export const createMockedIndexPattern = (): IndexPattern => ({ - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: [ +export const createMockedIndexPattern = (): IndexPattern => { + const fields = [ { name: 'timestamp', displayName: 'timestampLabel', @@ -74,16 +71,19 @@ export const createMockedIndexPattern = (): IndexPattern => ({ lang: 'painless', script: '1234', }, - ], -}); + ]; + return { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields, + getFieldByName: getFieldByNameFactory(fields), + }; +}; -export const createMockedRestrictedIndexPattern = () => ({ - id: '2', - title: 'my-fake-restricted-pattern', - timeFieldName: 'timestamp', - hasRestrictions: true, - fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, - fields: [ +export const createMockedRestrictedIndexPattern = () => { + const fields = [ { name: 'timestamp', displayName: 'timestampLabel', @@ -109,54 +109,63 @@ export const createMockedRestrictedIndexPattern = () => ({ lang: 'painless', script: '1234', }, - ], - typeMeta: { - params: { - rollup_index: 'my-fake-index-pattern', - }, - aggs: { - terms: { - source: { - agg: 'terms', - }, + ]; + return { + id: '2', + title: 'my-fake-restricted-pattern', + timeFieldName: 'timestamp', + hasRestrictions: true, + fieldFormatMap: { bytes: { id: 'bytes', params: { pattern: '0.0' } } }, + fields, + getFieldByName: getFieldByNameFactory(fields), + typeMeta: { + params: { + rollup_index: 'my-fake-index-pattern', }, - date_histogram: { - timestamp: { - agg: 'date_histogram', - fixed_interval: '1d', - delay: '7d', - time_zone: 'UTC', + aggs: { + terms: { + source: { + agg: 'terms', + }, }, - }, - histogram: { - bytes: { - agg: 'histogram', - interval: 1000, + date_histogram: { + timestamp: { + agg: 'date_histogram', + fixed_interval: '1d', + delay: '7d', + time_zone: 'UTC', + }, }, - }, - avg: { - bytes: { - agg: 'avg', + histogram: { + bytes: { + agg: 'histogram', + interval: 1000, + }, }, - }, - max: { - bytes: { - agg: 'max', + avg: { + bytes: { + agg: 'avg', + }, }, - }, - min: { - bytes: { - agg: 'min', + max: { + bytes: { + agg: 'max', + }, }, - }, - sum: { - bytes: { - agg: 'sum', + min: { + bytes: { + agg: 'min', + }, + }, + sum: { + bytes: { + agg: 'sum', + }, }, }, }, - }, -}); + }; +}; export function createMockedDragDropContext(): jest.Mocked { return { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 65119d3978ee6..1cfa63511a45c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -43,7 +43,7 @@ export const cardinalityOperation: OperationDefinition { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + const newField = newIndexPattern.getFieldByName(column.sourceField); return Boolean( newField && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index ac6bf63c37110..fc33b64ca508f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -18,6 +18,7 @@ import { } from '../../../../../../../src/plugins/data/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import { IndexPatternPrivateState } from '../../types'; +import { getFieldByNameFactory } from '../../pure_helpers'; const dataStart = dataPluginMock.createStartContract(); dataStart.search.aggs.calculateAutoTimeExpression = getCalculateAutoTimeExpression( @@ -66,6 +67,17 @@ describe('date_histogram', () => { searchable: true, }, ], + + getFieldByName: getFieldByNameFactory([ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + esTypes: ['date'], + aggregatable: true, + searchable: true, + }, + ]), }, 2: { id: '2', @@ -81,6 +93,16 @@ describe('date_histogram', () => { searchable: true, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'other_timestamp', + displayName: 'other_timestamp', + type: 'date', + esTypes: ['date'], + aggregatable: true, + searchable: true, + }, + ]), }, }, layers: { @@ -267,6 +289,22 @@ describe('date_histogram', () => { }, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'timestamp', + displayName: 'timestamp', + aggregatable: true, + searchable: true, + type: 'date', + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + time_zone: 'UTC', + calendar_interval: '42w', + }, + }, + }, + ]), } ); expect(esAggsConfig).toEqual( @@ -294,7 +332,7 @@ describe('date_histogram', () => { }, }; const indexPattern = createMockedIndexPattern(); - const newDateField = indexPattern.fields.find((i) => i.name === 'start_date')!; + const newDateField = indexPattern.getFieldByName('start_date')!; const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField); expect(column).toHaveProperty('sourceField', 'start_date'); @@ -314,7 +352,7 @@ describe('date_histogram', () => { }, }; const indexPattern = createMockedIndexPattern(); - const newDateField = indexPattern.fields.find((i) => i.name === 'start_date')!; + const newDateField = indexPattern.getFieldByName('start_date')!; const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField); expect(column).toHaveProperty('sourceField', 'start_date'); @@ -356,6 +394,22 @@ describe('date_histogram', () => { }, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'dateField', + displayName: 'dateField', + type: 'date', + aggregatable: true, + searchable: true, + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + time_zone: 'CET', + calendar_interval: 'w', + }, + }, + }, + ]), } ); expect(transferedColumn).toEqual( @@ -393,6 +447,15 @@ describe('date_histogram', () => { searchable: true, }, ], + getFieldByName: getFieldByNameFactory([ + { + name: 'dateField', + displayName: 'dateField', + type: 'date', + aggregatable: true, + searchable: true, + }, + ]), } ); expect(transferedColumn).toEqual( @@ -609,6 +672,18 @@ describe('date_histogram', () => { }, }, ], + getFieldByName: getFieldByNameFactory([ + { + ...state.indexPatterns[1].fields[0], + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + time_zone: 'UTC', + calendar_interval: '1h', + }, + }, + }, + ]), }, }, }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 185f44405bb4b..19043c03e5a61 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -81,7 +81,7 @@ export const dateHistogramOperation: OperationDefinition< }; }, isTransferable: (column, newIndexPattern) => { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + const newField = newIndexPattern.getFieldByName(column.sourceField); return Boolean( newField && @@ -91,12 +91,9 @@ export const dateHistogramOperation: OperationDefinition< ); }, transfer: (column, newIndexPattern) => { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); - if ( - newField && - newField.aggregationRestrictions && - newField.aggregationRestrictions.date_histogram - ) { + const newField = newIndexPattern.getFieldByName(column.sourceField); + + if (newField?.aggregationRestrictions?.date_histogram) { const restrictions = newField.aggregationRestrictions.date_histogram; return { @@ -123,7 +120,7 @@ export const dateHistogramOperation: OperationDefinition< }; }, toEsAggsConfig: (column, columnId, indexPattern) => { - const usedField = indexPattern.fields.find((field) => field.name === column.sourceField); + const usedField = indexPattern.getFieldByName(column.sourceField); return { id: columnId, enabled: true, @@ -132,7 +129,7 @@ export const dateHistogramOperation: OperationDefinition< params: { field: column.sourceField, time_zone: column.params.timeZone, - useNormalizedEsInterval: !usedField || !usedField.aggregationRestrictions?.date_histogram, + useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram, interval: column.params.interval, drop_partials: false, min_doc_count: 0, @@ -143,8 +140,8 @@ export const dateHistogramOperation: OperationDefinition< paramEditor: ({ state, setState, currentColumn, layerId, dateRange, data }) => { const field = currentColumn && - state.indexPatterns[state.layers[layerId].indexPatternId].fields.find( - (currentField) => currentField.name === currentColumn.sourceField + state.indexPatterns[state.layers[layerId].indexPatternId].getFieldByName( + currentColumn.sourceField ); const intervalIsRestricted = field!.aggregationRestrictions && field!.aggregationRestrictions.date_histogram; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index 1d3ecc165ce74..fef575c61475c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -43,7 +43,7 @@ function buildMetricOperation>({ } }, isTransferable: (column, newIndexPattern) => { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + const newField = newIndexPattern.getFieldByName(column.sourceField); return Boolean( newField && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index d43a905434c02..ce015284e544b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -30,6 +30,7 @@ import { } from './constants'; import { RangePopover } from './advanced_editor'; import { DragDropBuckets } from '../shared_components'; +import { getFieldByNameFactory } from '../../../pure_helpers'; const dataPluginMockValue = dataPluginMock.createStartContract(); // need to overwrite the formatter field first @@ -96,6 +97,9 @@ describe('ranges', () => { title: 'my_index_pattern', hasRestrictions: false, fields: [{ name: sourceField, type: 'number', displayName: sourceField }], + getFieldByName: getFieldByNameFactory([ + { name: sourceField, type: 'number', displayName: sourceField }, + ]), }, }, existingFields: {}, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 46d9e4e6c22de..c6cc6ae13f178 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -140,7 +140,7 @@ export const rangeOperation: OperationDefinition { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + const newField = newIndexPattern.getFieldByName(column.sourceField); return Boolean( newField && @@ -168,9 +168,7 @@ export const rangeOperation: OperationDefinition { const indexPattern = state.indexPatterns[state.layers[layerId].indexPatternId]; - const currentField = indexPattern.fields.find( - (field) => field.name === currentColumn.sourceField - ); + const currentField = indexPattern.getFieldByName(currentColumn.sourceField); const numberFormat = currentColumn.params.format; const numberFormatterPattern = numberFormat && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index dcb4646816e13..421068a5ad47f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -61,7 +61,7 @@ export const termsOperation: OperationDefinition { - const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + const newField = newIndexPattern.getFieldByName(column.sourceField); return Boolean( newField && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 1341ca0587c75..bb1b13ba74cc5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -103,7 +103,7 @@ describe('terms', () => { }, }; const indexPattern = createMockedIndexPattern(); - const newNumberField = indexPattern.fields.find((i) => i.name === 'bytes')!; + const newNumberField = indexPattern.getFieldByName('bytes')!; const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField); expect(column).toHaveProperty('dataType', 'number'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 6808bc724f26b..9767d4bdca688 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -8,38 +8,42 @@ import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColum import { AvgIndexPatternColumn } from './definitions/metrics'; import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; +import { getFieldByNameFactory } from '../pure_helpers'; jest.mock('../loader'); +const fields = [ + { + name: 'timestamp', + displayName: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, +]; + const expectedIndexPatterns = { 1: { id: '1', title: 'my-fake-index-pattern', timeFieldName: 'timestamp', hasRestrictions: false, - fields: [ - { - name: 'timestamp', - displayName: 'timestamp', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - displayName: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - }, - ], + fields, + getFieldByName: getFieldByNameFactory(fields), }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts index 9e81b5e0c5bf9..c5da3d0c5dcde 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/pure_helpers.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternPrivateState } from './types'; +import { keyBy } from 'lodash'; +import { IndexPatternField, IndexPatternPrivateState } from './types'; export function fieldExists( existingFields: IndexPatternPrivateState['existingFields'], @@ -13,3 +14,8 @@ export function fieldExists( ) { return existingFields[indexPatternTitle] && existingFields[indexPatternTitle][fieldName]; } + +export function getFieldByNameFactory(newFields: IndexPatternField[]) { + const fieldsLookup = keyBy(newFields, 'name'); + return (name: string) => fieldsLookup[name]; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts index da90a2ce5fcec..45008b2d9439a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts @@ -16,6 +16,7 @@ import { TermsIndexPatternColumn } from './operations/definitions/terms'; import { DateHistogramIndexPatternColumn } from './operations/definitions/date_histogram'; import { AvgIndexPatternColumn } from './operations/definitions/metrics'; import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; +import { getFieldByNameFactory } from './pure_helpers'; jest.mock('./operations'); @@ -585,59 +586,61 @@ describe('state_helpers', () => { }); describe('updateLayerIndexPattern', () => { - const indexPattern: IndexPattern = { - id: 'test', - title: '', - hasRestrictions: true, - fields: [ - { - name: 'fieldA', - displayName: 'fieldA', - aggregatable: true, - searchable: true, - type: 'string', - }, - { - name: 'fieldB', - displayName: 'fieldB', - aggregatable: true, - searchable: true, - type: 'number', - aggregationRestrictions: { - avg: { - agg: 'avg', - }, + const fields = [ + { + name: 'fieldA', + displayName: 'fieldA', + aggregatable: true, + searchable: true, + type: 'string', + }, + { + name: 'fieldB', + displayName: 'fieldB', + aggregatable: true, + searchable: true, + type: 'number', + aggregationRestrictions: { + avg: { + agg: 'avg', }, }, - { - name: 'fieldC', - displayName: 'fieldC', - aggregatable: false, - searchable: true, - type: 'date', - }, - { - name: 'fieldD', - displayName: 'fieldD', - aggregatable: true, - searchable: true, - type: 'date', - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - time_zone: 'CET', - calendar_interval: 'w', - }, + }, + { + name: 'fieldC', + displayName: 'fieldC', + aggregatable: false, + searchable: true, + type: 'date', + }, + { + name: 'fieldD', + displayName: 'fieldD', + aggregatable: true, + searchable: true, + type: 'date', + aggregationRestrictions: { + date_histogram: { + agg: 'date_histogram', + time_zone: 'CET', + calendar_interval: 'w', }, }, - { - name: 'fieldE', - displayName: 'fieldE', - aggregatable: true, - searchable: true, - type: 'date', - }, - ], + }, + { + name: 'fieldE', + displayName: 'fieldE', + aggregatable: true, + searchable: true, + type: 'date', + }, + ]; + const indexPattern: IndexPattern = { + id: 'test', + title: '', + hasRestrictions: true, + getFieldByName: getFieldByNameFactory(fields), + fields, }; it('should switch index pattern id in layer', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index a3c0e8aed7421..1e6fc5a5806b5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -11,6 +11,7 @@ import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/pub export interface IndexPattern { id: string; fields: IndexPatternField[]; + getFieldByName(name: string): IndexPatternField | undefined; title: string; timeFieldName?: string; fieldFormatMap?: Record< diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts index d3d65617f2253..d0ea81d135156 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts @@ -87,15 +87,15 @@ export function fieldIsInvalid( indexPattern: IndexPattern ) { const operationDefinition = operationType && operationDefinitionMap[operationType]; + const field = sourceField ? indexPattern.getFieldByName(sourceField) : undefined; return Boolean( sourceField && operationDefinition && - !indexPattern.fields.some( - (field) => - field.name === sourceField && - operationDefinition?.input === 'field' && - operationDefinition.getPossibleOperationForField(field) !== undefined + !( + field && + operationDefinition?.input === 'field' && + operationDefinition.getPossibleOperationForField(field) !== undefined ) ); } From dc489e48a4942a43a860f44d8a21f54aad210fc4 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Tue, 10 Nov 2020 14:14:25 +0000 Subject: [PATCH 48/87] [Discover] Add metric on adding filter (#82961) --- src/plugins/discover/public/application/angular/discover.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 389eda90e00a1..af763240bccfd 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -65,6 +65,7 @@ const { timefilter, toastNotifications, uiSettings: config, + trackUiMetric, } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; @@ -81,6 +82,7 @@ import { DOC_HIDE_TIME_COLUMN_SETTING, MODIFY_COLUMNS_ON_SWITCH, } from '../../../common'; +import { METRIC_TYPE } from '@kbn/analytics'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -990,6 +992,9 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise operation, $scope.indexPattern.id ); + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, 'filter_added'); + } return filterManager.addFilters(newFilters); }; From b2d6b66fe505b5af504073d448ed4b84785fdb39 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Tue, 10 Nov 2020 17:15:26 +0300 Subject: [PATCH 49/87] [bundle optimization] fix imports of react-use lib (#82847) * [bundle optimization] fix imports of react-use lib * add 2 more files * add rule into eslintrc.js Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .eslintrc.js | 4 ++++ .../home/public/application/components/home_app.js | 2 +- .../public/components/controls/date_ranges.tsx | 2 +- .../public/components/controls/field.tsx | 2 +- .../public/components/controls/filters.tsx | 2 +- .../public/components/controls/order_by.tsx | 2 +- .../public/components/controls/radius_ratio_option.tsx | 2 +- .../public/components/controls/sub_metric.tsx | 2 +- .../public/components/sidebar/controls.tsx | 2 +- .../application/components/visualize_listing.tsx | 4 +++- .../URLFilter/URLSearch/SelectableUrlList.tsx | 2 +- .../components/app/ServiceMap/useRefDimensions.ts | 2 +- .../plugins/fleet/public/applications/fleet/index.tsx | 2 +- .../expression_editor/criterion_preview_chart.tsx | 2 +- .../components/expression_editor/editor.tsx | 2 +- .../infra/public/components/log_stream/index.tsx | 2 +- .../setup_flyout/log_entry_categories_setup_view.tsx | 2 +- .../logs/log_analysis/log_analysis_setup_state.ts | 2 +- .../use_log_entry_categories_quality.ts | 2 +- .../infra/public/containers/logs/log_entries/index.ts | 2 +- .../containers/logs/log_highlights/log_highlights.tsx | 2 +- .../containers/logs/log_position/log_position_state.ts | 2 +- .../public/containers/logs/log_source/log_source.ts | 2 +- .../public/containers/logs/log_summary/with_summary.ts | 2 +- x-pack/plugins/infra/public/hooks/use_kibana_space.ts | 2 +- .../infra/public/hooks/use_kibana_timefilter_time.tsx | 5 ++++- .../public/pages/link_to/redirect_to_node_logs.tsx | 2 +- .../sections/top_categories/top_categories_table.tsx | 2 +- .../log_entry_rate/sections/anomalies/expanded_row.tsx | 2 +- .../logs/log_entry_rate/sections/anomalies/table.tsx | 2 +- .../log_entry_rate/use_log_entry_anomalies_results.ts | 2 +- .../plugins/infra/public/pages/logs/page_content.tsx | 2 +- .../pages/metrics/inventory_view/components/layout.tsx | 2 +- .../plugins/infra/public/utils/use_tracked_promise.ts | 2 +- .../operations/definitions/filters/filter_popover.tsx | 2 +- .../operations/definitions/ranges/advanced_editor.tsx | 2 +- .../operations/definitions/ranges/range_editor.tsx | 2 +- .../definitions/shared_components/label_input.tsx | 2 +- .../definitions/terms/values_range_input.test.tsx | 4 +--- .../definitions/terms/values_range_input.tsx | 2 +- .../plugins/lens/public/pie_visualization/toolbar.tsx | 2 +- .../logstash/public/application/pipeline_edit_view.tsx | 2 +- .../public/common/mock/endpoint/app_root_provider.tsx | 2 +- .../components/aggregation_list/popover_form.tsx | 2 +- .../common/filter_agg/components/filter_agg_form.tsx | 2 +- .../common/filter_agg/components/filter_term_form.tsx | 2 +- .../__tests__/step_screenshot_display.test.tsx | 10 ++++------ .../monitor/synthetics/step_screenshot_display.tsx | 2 +- 48 files changed, 59 insertions(+), 54 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5c2a2817eae53..561e9bc55bf9d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -640,6 +640,10 @@ module.exports = { name: 'lodash/fp/assocPath', message: 'Please use @elastic/safer-lodash-set instead', }, + { + name: 'react-use', + message: 'Please use react-use/lib/{method} instead.', + }, ], }, ], diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index 7fe4f4351c35b..734100fe584ab 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -28,7 +28,7 @@ import { HashRouter as Router, Switch, Route } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../kibana_services'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; const RedirectToDefaultApp = () => { useMount(() => { diff --git a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx index 785ef1b83a23d..90ee1dd4f02ae 100644 --- a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx @@ -36,7 +36,7 @@ import dateMath from '@elastic/datemath'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { isEqual, omit } from 'lodash'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { DocLinksStart } from 'src/core/public'; import { useKibana } from '../../../../kibana_react/public'; diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx index 9529adfe12720..cb6e9d9aa7ba8 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -19,7 +19,7 @@ import { get } from 'lodash'; import React, { useState, useCallback } from 'react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; diff --git a/src/plugins/vis_default_editor/public/components/controls/filters.tsx b/src/plugins/vis_default_editor/public/components/controls/filters.tsx index b2e6373edfc10..4c5181ab316d1 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filters.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filters.tsx @@ -21,7 +21,7 @@ import React, { useState, useEffect } from 'react'; import { omit, isEqual } from 'lodash'; import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { Query, DataPublicPluginStart } from '../../../../data/public'; import { IUiSettingsClient } from '../../../../../core/public'; diff --git a/src/plugins/vis_default_editor/public/components/controls/order_by.tsx b/src/plugins/vis_default_editor/public/components/controls/order_by.tsx index 16aeafaab253b..000719318f107 100644 --- a/src/plugins/vis_default_editor/public/components/controls/order_by.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/order_by.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { isCompatibleAggregation, diff --git a/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx b/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx index c2c21e7c1a058..00e6b6da88b05 100644 --- a/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx @@ -21,7 +21,7 @@ import React, { useCallback } from 'react'; import { EuiFormRow, EuiIconTip, EuiRange, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { AggControlProps } from './agg_control_props'; diff --git a/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx index fc79ba703c2b4..4b0637edf4055 100644 --- a/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { AggParamType, IAggConfig, AggGroupNames } from '../../../../data/public'; import { useSubAggParamsHandlers } from './utils'; diff --git a/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx b/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx index df9818b237962..388ac39f06475 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx @@ -21,7 +21,7 @@ import React, { useCallback, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { Vis } from 'src/plugins/visualizations/public'; import { discardChanges, EditorAction } from './state'; diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx index 2edabbf46f9d8..718bd2ed343ce 100644 --- a/src/plugins/visualize/public/application/components/visualize_listing.tsx +++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx @@ -21,7 +21,9 @@ import './visualize_listing.scss'; import React, { useCallback, useRef, useMemo, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { useUnmount, useMount } from 'react-use'; +import useUnmount from 'react-use/lib/useUnmount'; +import useMount from 'react-use/lib/useMount'; + import { useLocation } from 'react-router-dom'; import { SavedObjectsFindOptionsReference } from '../../../../../core/public'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx index 7bd9b2c87814b..1d314e3a0e0d3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -31,7 +31,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import { useEvent } from 'react-use'; +import useEvent from 'react-use/lib/useEvent'; import { formatOptions, selectableRenderOptions, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/useRefDimensions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/useRefDimensions.ts index c8639b334f66a..fc478e27ccac3 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/useRefDimensions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/useRefDimensions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { useRef } from 'react'; -import { useWindowSize } from 'react-use'; +import useWindowSize from 'react-use/lib/useWindowSize'; export function useRefDimensions() { const ref = useRef(null); diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index 03cc4a8b7aff7..a49306f8e8d55 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -5,7 +5,7 @@ */ import React, { memo, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { HashRouter as Router, Redirect, Switch, Route, RouteProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx index 478d4b879a7e1..1d23b4d4778ca 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx @@ -5,7 +5,7 @@ */ import React, { useMemo } from 'react'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { ScaleType, AnnotationDomainTypes, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index c5b0ed5844852..48639f3095d3d 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiLoadingSpinner, EuiSpacer, EuiButton, EuiCallOut } from '@elastic/eui'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { GroupByExpression } from '../../../common/group_by_expression/group_by_expression'; import { ForLastExpression, diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index f9bfbf9564798..6698018e8cc19 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -6,7 +6,7 @@ import React, { useMemo } from 'react'; import { noop } from 'lodash'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { euiStyled } from '../../../../observability/public'; import { LogEntriesCursor } from '../../../common/http_api'; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx index e7961a11a4d52..5ff15012ad245 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/log_entry_categories_setup_view.tsx @@ -6,7 +6,7 @@ import { EuiSpacer, EuiSteps, EuiText, EuiTitle } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { useLogEntryCategoriesSetup } from '../../../../containers/logs/log_analysis/modules/log_entry_categories'; import { createInitialConfigurationStep } from '../initial_configuration_step'; import { createProcessStep } from '../process_step'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts index 750a7104a3a98..821d0373d316a 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.ts @@ -6,7 +6,7 @@ import { isEqual } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { usePrevious } from 'react-use'; +import usePrevious from 'react-use/lib/usePrevious'; import { combineDatasetFilters, DatasetFilter, diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts index 6bad94ec49f87..c490f7d74fc9d 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_quality.ts @@ -5,7 +5,7 @@ */ import { useMemo, useState } from 'react'; -import { useDeepCompareEffect } from 'react-use'; +import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; import { CategoryQualityWarningReason, QualityWarning, diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 214bb16b24283..146746af980c9 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { useEffect, useState, useReducer, useCallback } from 'react'; -import { useMountedState } from 'react-use'; +import useMountedState from 'react-use/lib/useMountedState'; import createContainer from 'constate'; import { pick, throttle } from 'lodash'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index 941e89848131b..3081e37da1d37 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -6,7 +6,7 @@ import createContainer from 'constate'; import { useState, useContext } from 'react'; -import { useThrottle } from 'react-use'; +import useThrottle from 'react-use/lib/useThrottle'; import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_position/log_position_state.ts b/x-pack/plugins/infra/public/containers/logs/log_position/log_position_state.ts index 4b110fbc4e51e..f1aed0fa75165 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_position/log_position_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_position/log_position_state.ts @@ -6,7 +6,7 @@ import { useState, useMemo, useEffect, useCallback } from 'react'; import createContainer from 'constate'; -import { useSetState } from 'react-use'; +import useSetState from 'react-use/lib/useSetState'; import { TimeKey } from '../../../../common/time'; import { datemathToEpochMillis, isValidDatemath } from '../../../utils/datemath'; import { useKibanaTimefilterTime } from '../../../hooks/use_kibana_timefilter_time'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index e2dd4c523c03f..75c328b829397 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -6,7 +6,7 @@ import createContainer from 'constate'; import { useCallback, useMemo, useState } from 'react'; -import { useMountedState } from 'react-use'; +import useMountedState from 'react-use/lib/useMountedState'; import type { HttpHandler } from 'src/core/public'; import { LogSourceConfiguration, diff --git a/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts b/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts index 625a1ead4d930..1a420d7dddf91 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_summary/with_summary.ts @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { useThrottle } from 'react-use'; +import useThrottle from 'react-use/lib/useThrottle'; import { RendererFunction } from '../../../utils/typed_react'; import { LogSummaryBuckets, useLogSummary } from './log_summary'; diff --git a/x-pack/plugins/infra/public/hooks/use_kibana_space.ts b/x-pack/plugins/infra/public/hooks/use_kibana_space.ts index 1c06263008961..39caa4f063cd5 100644 --- a/x-pack/plugins/infra/public/hooks/use_kibana_space.ts +++ b/x-pack/plugins/infra/public/hooks/use_kibana_space.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; import { useKibanaContextForPlugin } from '../hooks/use_kibana'; import type { Space } from '../../../spaces/public'; diff --git a/x-pack/plugins/infra/public/hooks/use_kibana_timefilter_time.tsx b/x-pack/plugins/infra/public/hooks/use_kibana_timefilter_time.tsx index e9537370f12cd..193a285c6b882 100644 --- a/x-pack/plugins/infra/public/hooks/use_kibana_timefilter_time.tsx +++ b/x-pack/plugins/infra/public/hooks/use_kibana_timefilter_time.tsx @@ -5,7 +5,10 @@ */ import { useCallback } from 'react'; -import { useUpdateEffect, useMount } from 'react-use'; + +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; +import useMount from 'react-use/lib/useMount'; + import { useKibanaContextForPlugin } from './use_kibana'; import { TimeRange, TimefilterContract } from '../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 0541b811508ee..6205d9883e535 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import flowRight from 'lodash/flowRight'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { HttpStart } from 'src/core/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { findInventoryFields } from '../../../common/inventory_models'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx index 4fd8aa3cdc1dd..ab4195492a706 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; -import { useSet } from 'react-use'; +import useSet from 'react-use/lib/useSet'; import { euiStyled } from '../../../../../../../observability/public'; import { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 84ef13cc70706..9c78c421af752 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiTitle } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { euiStyled } from '../../../../../../../observability/public'; import { LogEntryAnomaly } from '../../../../../../common/http_api'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index e0a3b6fb91db0..855113d66f510 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -17,7 +17,7 @@ import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo } from 'react'; -import { useSet } from 'react-use'; +import useSet from 'react-use/lib/useSet'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { formatAnomalyScore, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts index 37c99272f0872..e626e7477f2c0 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts @@ -5,7 +5,7 @@ */ import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; import { callGetLogEntryAnomaliesAPI } from './service_calls/get_log_entry_anomalies'; import { callGetLogEntryAnomaliesDatasetsAPI } from './service_calls/get_log_entry_anomalies_datasets'; diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index e85f0d9bf446b..10189c0d8076c 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; -import { useMount } from 'react-use'; +import useMount from 'react-use/lib/useMount'; import { AlertDropdown } from '../../alerting/log_threshold'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; 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 b9caef704d071..f6e9d45c4d225 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 @@ -5,7 +5,7 @@ */ import React, { useCallback, useEffect } from 'react'; -import { useInterval } from 'react-use'; +import useInterval from 'react-use/lib/useInterval'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AutoSizer } from '../../../../components/auto_sizer'; diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts index 42518127f68bf..75c9d1bacc675 100644 --- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts @@ -7,7 +7,7 @@ /* eslint-disable max-classes-per-file */ import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react'; -import { useMountedState } from 'react-use'; +import useMountedState from 'react-use/lib/useMountedState'; interface UseTrackedPromiseArgs { createPromise: (...args: Arguments) => Promise; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index 077e07a89f788..b023a9a5a3ec5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -6,7 +6,7 @@ import './filter_popover.scss'; import React, { MouseEventHandler, useState } from 'react'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { EuiPopover, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FilterValue, defaultLabel, isQueryValid } from '.'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index c6773e806aef8..2eb971aa03c55 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -8,7 +8,7 @@ import './advanced_editor.scss'; import React, { useState, MouseEventHandler } from 'react'; import { i18n } from '@kbn/i18n'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { EuiFlexGroup, EuiFlexItem, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx index 8ed17a813e7fd..a18c47f9dedd1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { EuiButtonEmpty, EuiFormRow, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx index 09a98d9e48ef7..f0ee30bb4331b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx @@ -5,7 +5,7 @@ */ import React, { useState, useEffect } from 'react'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { EuiFieldText, keys } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx index c1620dd316a60..18b9b5b1e8b98 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx @@ -10,9 +10,7 @@ import { shallow } from 'enzyme'; import { EuiRange } from '@elastic/eui'; import { ValuesRangeInput } from './values_range_input'; -jest.mock('react-use', () => ({ - useDebounce: (fn: () => void) => fn(), -})); +jest.mock('react-use/lib/useDebounce', () => (fn: () => void) => fn()); describe('ValuesRangeInput', () => { it('should render EuiRange correctly', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx index 6bfde4b652571..ef42f2d4a7175 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx @@ -5,7 +5,7 @@ */ import React, { useState } from 'react'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; import { EuiRange } from '@elastic/eui'; diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index d69164de8a6aa..f1eb4ccdd9cf7 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -6,7 +6,7 @@ import './toolbar.scss'; import React, { useState } from 'react'; -import { useDebounce } from 'react-use'; +import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, diff --git a/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx b/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx index 9b291e5261033..a36ef394f3327 100644 --- a/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx +++ b/x-pack/plugins/logstash/public/application/pipeline_edit_view.tsx @@ -5,7 +5,7 @@ */ import React, { useState, useLayoutEffect, useCallback } from 'react'; -import { usePromise } from 'react-use'; +import usePromise from 'react-use/lib/usePromise'; import { History } from 'history'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx index 3b7262e8a8d7e..fd6a483e538b8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx @@ -9,7 +9,7 @@ import { Provider } from 'react-redux'; import { I18nProvider } from '@kbn/i18n/react'; import { Router } from 'react-router-dom'; import { History } from 'history'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { Store } from 'redux'; import { EuiThemeProvider } from '../../../../../xpack_legacy/common'; import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx index 30e8c2b594db7..dbdff6c6b311e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { cloneDeep } from 'lodash'; -import { useUpdateEffect } from 'react-use'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { AggName } from '../../../../../../common/types/aggregations'; import { dictionaryToArray } from '../../../../../../common/types/common'; import { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx index ac6e93d3ed5eb..4e45ec7689235 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx @@ -7,7 +7,7 @@ import React, { useContext, useMemo } from 'react'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUpdateEffect } from 'react-use'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { CreateTransformWizardContext } from '../../../../wizard/wizard'; import { commonFilterAggs, filterAggsFieldSupport } from '../constants'; import { IndexPattern } from '../../../../../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index d59f99192621c..f9f345610e1eb 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { EuiComboBox, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { debounce } from 'lodash'; -import { useUpdateEffect } from 'react-use'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { i18n } from '@kbn/i18n'; import { isEsSearchResponse } from '../../../../../../../../../common/api_schemas/type_guards'; import { useApi } from '../../../../../../../hooks'; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx index 16db430dbd73a..9d94514aa48c5 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx @@ -6,15 +6,13 @@ import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; -import * as reactUse from 'react-use'; import { StepScreenshotDisplay } from '../step_screenshot_display'; -describe('StepScreenshotDisplayProps', () => { - // @ts-ignore missing fields don't matter in this test, the component in question only relies on `isIntersecting` - jest.spyOn(reactUse, 'useIntersection').mockImplementation(() => ({ - isIntersecting: true, - })); +jest.mock('react-use/lib/useIntersection', () => () => ({ + isIntersecting: true, +})); +describe('StepScreenshotDisplayProps', () => { it('displays screenshot thumbnail when present', () => { const wrapper = mountWithIntl( Date: Tue, 10 Nov 2020 15:16:58 +0100 Subject: [PATCH 50/87] migrate i18n mixin to KP (#81799) * migrate i18n mixin to KP * directly load the config from i18n service * add base tests * update telemetry schema * update legacy telemetry schema * fix server tests * use paths from config service instead of manually loading the plugin config * add tests for get_translation_paths * add tests for i18n service * add plugin service test * update generated doc * improve tsdoc --- ...ibana-plugin-core-server.coresetup.i18n.md | 13 +++ .../kibana-plugin-core-server.coresetup.md | 1 + ...-core-server.i18nservicesetup.getlocale.md | 17 ++++ ...er.i18nservicesetup.gettranslationfiles.md | 17 ++++ ...ana-plugin-core-server.i18nservicesetup.md | 20 +++++ .../core/server/kibana-plugin-core-server.md | 1 + .../environment/environment_service.mock.ts | 3 +- .../constants.ts => core/server/i18n/fs.ts} | 5 +- .../i18n/get_kibana_translation_files.test.ts | 81 +++++++++++++++++ .../i18n/get_kibana_translation_files.ts} | 29 +++--- .../i18n/get_translation_paths.test.mocks.ts | 26 ++++++ .../server/i18n/get_translation_paths.test.ts | 90 +++++++++++++++++++ .../server/i18n/get_translation_paths.ts | 12 +-- src/core/server/i18n/i18n_config.ts | 29 ++++++ src/core/server/i18n/i18n_service.mock.ts | 50 +++++++++++ .../server/i18n/i18n_service.test.mocks.ts | 28 ++++++ src/core/server/i18n/i18n_service.test.ts | 84 +++++++++++++++++ src/core/server/i18n/i18n_service.ts | 75 ++++++++++++++++ src/{legacy => core}/server/i18n/index.ts | 3 +- src/core/server/i18n/init_translations.ts | 31 +++++++ src/core/server/index.ts | 5 ++ src/core/server/internal_types.ts | 2 + src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 1 + .../server/metrics/metrics_service.mock.ts | 2 +- src/core/server/mocks.ts | 4 + src/core/server/plugins/plugin_context.ts | 1 + .../server/plugins/plugins_service.mock.ts | 2 +- .../server/plugins/plugins_service.test.ts | 18 ++++ src/core/server/plugins/plugins_service.ts | 1 + .../server/plugins/plugins_system.test.ts | 9 ++ src/core/server/plugins/plugins_system.ts | 4 + .../rendering/__mocks__/rendering_service.ts | 4 +- .../saved_objects_service.mock.ts | 3 +- src/core/server/server.api.md | 8 ++ src/core/server/server.test.mocks.ts | 6 ++ src/core/server/server.test.ts | 7 ++ src/core/server/server.ts | 15 +++- src/core/server/status/status_service.mock.ts | 2 +- .../ui_settings/ui_settings_service.mock.ts | 2 +- src/legacy/server/config/schema.js | 13 +-- .../i18n/get_kibana_translation_paths.test.ts | 58 ------------ src/legacy/server/i18n/i18n_mixin.ts | 63 ------------- src/legacy/server/kbn_server.js | 4 - .../server/__snapshots__/index.test.ts.snap | 2 + .../server/collectors/index.ts | 1 + .../localization/file_integrity.test.mocks.ts | 0 .../localization/file_integrity.test.ts | 0 .../localization/file_integrity.ts | 0 .../server/collectors}/localization/index.ts | 0 .../telemetry_localization_collector.test.ts | 0 .../telemetry_localization_collector.ts | 18 ++-- .../kibana_usage_collection/server/plugin.ts | 2 + .../telemetry/schema/legacy_plugins.json | 20 +---- src/plugins/telemetry/schema/oss_plugins.json | 17 ++++ 55 files changed, 709 insertions(+), 202 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.i18n.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.getlocale.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.md rename src/{legacy/server/i18n/constants.ts => core/server/i18n/fs.ts} (88%) create mode 100644 src/core/server/i18n/get_kibana_translation_files.test.ts rename src/{legacy/server/i18n/get_kibana_translation_paths.ts => core/server/i18n/get_kibana_translation_files.ts} (64%) create mode 100644 src/core/server/i18n/get_translation_paths.test.mocks.ts create mode 100644 src/core/server/i18n/get_translation_paths.test.ts rename src/{legacy => core}/server/i18n/get_translation_paths.ts (85%) create mode 100644 src/core/server/i18n/i18n_config.ts create mode 100644 src/core/server/i18n/i18n_service.mock.ts create mode 100644 src/core/server/i18n/i18n_service.test.mocks.ts create mode 100644 src/core/server/i18n/i18n_service.test.ts create mode 100644 src/core/server/i18n/i18n_service.ts rename src/{legacy => core}/server/i18n/index.ts (86%) create mode 100644 src/core/server/i18n/init_translations.ts delete mode 100644 src/legacy/server/i18n/get_kibana_translation_paths.test.ts delete mode 100644 src/legacy/server/i18n/i18n_mixin.ts rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/file_integrity.test.mocks.ts (100%) rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/file_integrity.test.ts (100%) rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/file_integrity.ts (100%) rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/index.ts (100%) rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/telemetry_localization_collector.test.ts (100%) rename src/{legacy/server/i18n => plugins/kibana_usage_collection/server/collectors}/localization/telemetry_localization_collector.ts (82%) diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.i18n.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.i18n.md new file mode 100644 index 0000000000000..cac878c1e4449 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.i18n.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [i18n](./kibana-plugin-core-server.coresetup.i18n.md) + +## CoreSetup.i18n property + +[I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) + +Signature: + +```typescript +i18n: I18nServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 7a733cc34dace..1171dbad570ce 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -21,6 +21,7 @@ export interface CoreSetupElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | +| [i18n](./kibana-plugin-core-server.coresetup.i18n.md) | I18nServiceSetup | [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) | | [logging](./kibana-plugin-core-server.coresetup.logging.md) | LoggingServiceSetup | [LoggingServiceSetup](./kibana-plugin-core-server.loggingservicesetup.md) | | [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.getlocale.md b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.getlocale.md new file mode 100644 index 0000000000000..2fe8e564e7ce5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.getlocale.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) > [getLocale](./kibana-plugin-core-server.i18nservicesetup.getlocale.md) + +## I18nServiceSetup.getLocale() method + +Return the locale currently in use. + +Signature: + +```typescript +getLocale(): string; +``` +Returns: + +`string` + diff --git a/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md new file mode 100644 index 0000000000000..81caed287454e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) > [getTranslationFiles](./kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md) + +## I18nServiceSetup.getTranslationFiles() method + +Return the absolute paths to translation files currently in use. + +Signature: + +```typescript +getTranslationFiles(): string[]; +``` +Returns: + +`string[]` + diff --git a/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.md new file mode 100644 index 0000000000000..f68b7877953e7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.i18nservicesetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) + +## I18nServiceSetup interface + + +Signature: + +```typescript +export interface I18nServiceSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [getLocale()](./kibana-plugin-core-server.i18nservicesetup.getlocale.md) | Return the locale currently in use. | +| [getTranslationFiles()](./kibana-plugin-core-server.i18nservicesetup.gettranslationfiles.md) | Return the absolute paths to translation files currently in use. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 68f5e72915556..adbb2460dc80a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -89,6 +89,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpServerInfo](./kibana-plugin-core-server.httpserverinfo.md) | | | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) | | +| [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) | | | [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)). | | [IContextContainer](./kibana-plugin-core-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | | [ICspConfig](./kibana-plugin-core-server.icspconfig.md) | CSP configuration for use in Kibana. | diff --git a/src/core/server/environment/environment_service.mock.ts b/src/core/server/environment/environment_service.mock.ts index a956e369ba4a7..3c579b0f68b00 100644 --- a/src/core/server/environment/environment_service.mock.ts +++ b/src/core/server/environment/environment_service.mock.ts @@ -17,8 +17,7 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; - -import { EnvironmentService, InternalEnvironmentServiceSetup } from './environment_service'; +import type { EnvironmentService, InternalEnvironmentServiceSetup } from './environment_service'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { diff --git a/src/legacy/server/i18n/constants.ts b/src/core/server/i18n/fs.ts similarity index 88% rename from src/legacy/server/i18n/constants.ts rename to src/core/server/i18n/fs.ts index a7a410dbcb5b3..23d729504f81c 100644 --- a/src/legacy/server/i18n/constants.ts +++ b/src/core/server/i18n/fs.ts @@ -17,4 +17,7 @@ * under the License. */ -export const I18N_RC = '.i18nrc.json'; +import Fs from 'fs'; +import { promisify } from 'util'; + +export const readFile = promisify(Fs.readFile); diff --git a/src/core/server/i18n/get_kibana_translation_files.test.ts b/src/core/server/i18n/get_kibana_translation_files.test.ts new file mode 100644 index 0000000000000..737d8ed8bc4e2 --- /dev/null +++ b/src/core/server/i18n/get_kibana_translation_files.test.ts @@ -0,0 +1,81 @@ +/* + * 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 { getKibanaTranslationFiles } from './get_kibana_translation_files'; +import { getTranslationPaths } from './get_translation_paths'; + +const mockGetTranslationPaths = getTranslationPaths as jest.Mock; + +jest.mock('./get_translation_paths', () => ({ + getTranslationPaths: jest.fn().mockResolvedValue([]), +})); +jest.mock('../utils', () => ({ + fromRoot: jest.fn().mockImplementation((path: string) => path), +})); + +const locale = 'en'; + +describe('getKibanaTranslationPaths', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls getTranslationPaths against kibana root and kibana-extra', async () => { + await getKibanaTranslationFiles(locale, []); + + expect(mockGetTranslationPaths).toHaveBeenCalledTimes(2); + + expect(mockGetTranslationPaths).toHaveBeenCalledWith({ + cwd: '.', + nested: true, + }); + + expect(mockGetTranslationPaths).toHaveBeenCalledWith({ + cwd: '../kibana-extra', + nested: true, + }); + }); + + it('calls getTranslationPaths for each config returned in plugin.paths and plugins.scanDirs', async () => { + const pluginPaths = ['/path/to/pluginA', '/path/to/pluginB']; + + await getKibanaTranslationFiles(locale, pluginPaths); + + expect(mockGetTranslationPaths).toHaveBeenCalledTimes(2 + pluginPaths.length); + + pluginPaths.forEach((pluginPath) => { + expect(mockGetTranslationPaths).toHaveBeenCalledWith({ + cwd: pluginPath, + nested: false, + }); + }); + }); + + it('only return files for specified locale', async () => { + mockGetTranslationPaths.mockResolvedValueOnce(['/root/en.json', '/root/fr.json']); + mockGetTranslationPaths.mockResolvedValueOnce([ + '/kibana-extra/en.json', + '/kibana-extra/fr.json', + ]); + + const translationFiles = await getKibanaTranslationFiles('en', []); + + expect(translationFiles).toEqual(['/root/en.json', '/kibana-extra/en.json']); + }); +}); diff --git a/src/legacy/server/i18n/get_kibana_translation_paths.ts b/src/core/server/i18n/get_kibana_translation_files.ts similarity index 64% rename from src/legacy/server/i18n/get_kibana_translation_paths.ts rename to src/core/server/i18n/get_kibana_translation_files.ts index d7f77d3185ba4..dacb6a1e16a5c 100644 --- a/src/legacy/server/i18n/get_kibana_translation_paths.ts +++ b/src/core/server/i18n/get_kibana_translation_files.ts @@ -17,26 +17,27 @@ * under the License. */ -import { KibanaConfig } from '../kbn_server'; -import { fromRoot } from '../../../core/server/utils'; -import { I18N_RC } from './constants'; +import { basename } from 'path'; +import { fromRoot } from '../utils'; import { getTranslationPaths } from './get_translation_paths'; -export async function getKibanaTranslationPaths(config: Pick) { - return await Promise.all([ +export const getKibanaTranslationFiles = async ( + locale: string, + pluginPaths: string[] +): Promise => { + const translationPaths = await Promise.all([ getTranslationPaths({ cwd: fromRoot('.'), - glob: `*/${I18N_RC}`, + nested: true, }), - ...(config.get('plugins.paths') as string[]).map((cwd) => - getTranslationPaths({ cwd, glob: I18N_RC }) - ), - ...(config.get('plugins.scanDirs') as string[]).map((cwd) => - getTranslationPaths({ cwd, glob: `*/${I18N_RC}` }) - ), + ...pluginPaths.map((pluginPath) => getTranslationPaths({ cwd: pluginPath, nested: false })), getTranslationPaths({ cwd: fromRoot('../kibana-extra'), - glob: `*/${I18N_RC}`, + nested: true, }), ]); -} + + return ([] as string[]) + .concat(...translationPaths) + .filter((translationPath) => basename(translationPath, '.json') === locale); +}; diff --git a/src/core/server/i18n/get_translation_paths.test.mocks.ts b/src/core/server/i18n/get_translation_paths.test.mocks.ts new file mode 100644 index 0000000000000..f3b688062523c --- /dev/null +++ b/src/core/server/i18n/get_translation_paths.test.mocks.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 const globbyMock = jest.fn(); +jest.doMock('globby', () => globbyMock); + +export const readFileMock = jest.fn(); +jest.doMock('./fs', () => ({ + readFile: readFileMock, +})); diff --git a/src/core/server/i18n/get_translation_paths.test.ts b/src/core/server/i18n/get_translation_paths.test.ts new file mode 100644 index 0000000000000..c6af59da07fb5 --- /dev/null +++ b/src/core/server/i18n/get_translation_paths.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { resolve, join } from 'path'; +import { globbyMock, readFileMock } from './get_translation_paths.test.mocks'; +import { getTranslationPaths } from './get_translation_paths'; + +describe('getTranslationPaths', () => { + beforeEach(() => { + globbyMock.mockReset(); + readFileMock.mockReset(); + + globbyMock.mockResolvedValue([]); + readFileMock.mockResolvedValue('{}'); + }); + + it('calls `globby` with the correct parameters', async () => { + getTranslationPaths({ cwd: '/some/cwd', nested: false }); + + expect(globbyMock).toHaveBeenCalledTimes(1); + expect(globbyMock).toHaveBeenCalledWith('.i18nrc.json', { cwd: '/some/cwd' }); + + globbyMock.mockClear(); + + await getTranslationPaths({ cwd: '/other/cwd', nested: true }); + + expect(globbyMock).toHaveBeenCalledTimes(1); + expect(globbyMock).toHaveBeenCalledWith('*/.i18nrc.json', { cwd: '/other/cwd' }); + }); + + it('calls `readFile` for each entry returned by `globby`', async () => { + const entries = [join('pathA', '.i18nrc.json'), join('pathB', '.i18nrc.json')]; + globbyMock.mockResolvedValue(entries); + + const cwd = '/kibana-extra'; + + await getTranslationPaths({ cwd, nested: true }); + + expect(readFileMock).toHaveBeenCalledTimes(2); + + expect(readFileMock).toHaveBeenNthCalledWith(1, resolve(cwd, entries[0]), 'utf8'); + expect(readFileMock).toHaveBeenNthCalledWith(2, resolve(cwd, entries[1]), 'utf8'); + }); + + it('returns the absolute path to the translation files', async () => { + const entries = ['.i18nrc.json']; + globbyMock.mockResolvedValue(entries); + + const i18nFileContent = { + translations: ['translations/en.json', 'translations/fr.json'], + }; + readFileMock.mockResolvedValue(JSON.stringify(i18nFileContent)); + + const cwd = '/cwd'; + + const translationPaths = await getTranslationPaths({ cwd, nested: true }); + + expect(translationPaths).toEqual([ + resolve(cwd, 'translations/en.json'), + resolve(cwd, 'translations/fr.json'), + ]); + }); + + it('throws if i18nrc parsing fails', async () => { + globbyMock.mockResolvedValue(['.i18nrc.json']); + readFileMock.mockRejectedValue(new Error('error parsing file')); + + await expect( + getTranslationPaths({ cwd: '/cwd', nested: true }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to parse .i18nrc.json file at /cwd/.i18nrc.json"` + ); + }); +}); diff --git a/src/legacy/server/i18n/get_translation_paths.ts b/src/core/server/i18n/get_translation_paths.ts similarity index 85% rename from src/legacy/server/i18n/get_translation_paths.ts rename to src/core/server/i18n/get_translation_paths.ts index a2a292e2278be..41d9dc4722e37 100644 --- a/src/legacy/server/i18n/get_translation_paths.ts +++ b/src/core/server/i18n/get_translation_paths.ts @@ -17,18 +17,18 @@ * under the License. */ -import { promisify } from 'util'; -import { readFile } from 'fs'; import { resolve, dirname } from 'path'; import globby from 'globby'; - -const readFileAsync = promisify(readFile); +import { readFile } from './fs'; interface I18NRCFileStructure { translations?: string[]; } -export async function getTranslationPaths({ cwd, glob }: { cwd: string; glob: string }) { +const I18N_RC = '.i18nrc.json'; + +export async function getTranslationPaths({ cwd, nested }: { cwd: string; nested: boolean }) { + const glob = nested ? `*/${I18N_RC}` : I18N_RC; const entries = await globby(glob, { cwd }); const translationPaths: string[] = []; @@ -36,7 +36,7 @@ export async function getTranslationPaths({ cwd, glob }: { cwd: string; glob: st const entryFullPath = resolve(cwd, entry); const pluginBasePath = dirname(entryFullPath); try { - const content = await readFileAsync(entryFullPath, 'utf8'); + const content = await readFile(entryFullPath, 'utf8'); const { translations } = JSON.parse(content) as I18NRCFileStructure; if (translations && translations.length) { translations.forEach((translation) => { diff --git a/src/core/server/i18n/i18n_config.ts b/src/core/server/i18n/i18n_config.ts new file mode 100644 index 0000000000000..f181c52dc4b06 --- /dev/null +++ b/src/core/server/i18n/i18n_config.ts @@ -0,0 +1,29 @@ +/* + * 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, TypeOf } from '@kbn/config-schema'; + +export const config = { + path: 'i18n', + schema: schema.object({ + locale: schema.string({ defaultValue: 'en' }), + }), +}; + +export type I18nConfigType = TypeOf; diff --git a/src/core/server/i18n/i18n_service.mock.ts b/src/core/server/i18n/i18n_service.mock.ts new file mode 100644 index 0000000000000..679751aefbf27 --- /dev/null +++ b/src/core/server/i18n/i18n_service.mock.ts @@ -0,0 +1,50 @@ +/* + * 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 { PublicMethodsOf } from '@kbn/utility-types'; +import type { I18nServiceSetup, I18nService } from './i18n_service'; + +const createSetupContractMock = () => { + const mock: jest.Mocked = { + getLocale: jest.fn(), + getTranslationFiles: jest.fn(), + }; + + mock.getLocale.mockReturnValue('en'); + mock.getTranslationFiles.mockReturnValue([]); + + return mock; +}; + +type I18nServiceContract = PublicMethodsOf; + +const createMock = () => { + const mock: jest.Mocked = { + setup: jest.fn(), + }; + + mock.setup.mockResolvedValue(createSetupContractMock()); + + return mock; +}; + +export const i18nServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/i18n/i18n_service.test.mocks.ts b/src/core/server/i18n/i18n_service.test.mocks.ts new file mode 100644 index 0000000000000..23f97a1404fff --- /dev/null +++ b/src/core/server/i18n/i18n_service.test.mocks.ts @@ -0,0 +1,28 @@ +/* + * 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 const getKibanaTranslationFilesMock = jest.fn(); +jest.doMock('./get_kibana_translation_files', () => ({ + getKibanaTranslationFiles: getKibanaTranslationFilesMock, +})); + +export const initTranslationsMock = jest.fn(); +jest.doMock('./init_translations', () => ({ + initTranslations: initTranslationsMock, +})); diff --git a/src/core/server/i18n/i18n_service.test.ts b/src/core/server/i18n/i18n_service.test.ts new file mode 100644 index 0000000000000..87de39a92ab26 --- /dev/null +++ b/src/core/server/i18n/i18n_service.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { getKibanaTranslationFilesMock, initTranslationsMock } from './i18n_service.test.mocks'; + +import { BehaviorSubject } from 'rxjs'; +import { I18nService } from './i18n_service'; + +import { configServiceMock } from '../config/mocks'; +import { mockCoreContext } from '../core_context.mock'; + +const getConfigService = (locale = 'en') => { + const configService = configServiceMock.create(); + configService.atPath.mockImplementation((path) => { + if (path === 'i18n') { + return new BehaviorSubject({ + locale, + }); + } + return new BehaviorSubject({}); + }); + return configService; +}; + +describe('I18nService', () => { + let service: I18nService; + let configService: ReturnType; + + beforeEach(() => { + jest.clearAllMocks(); + configService = getConfigService(); + + const coreContext = mockCoreContext.create({ configService }); + service = new I18nService(coreContext); + }); + + describe('#setup', () => { + it('calls `getKibanaTranslationFiles` with the correct parameters', async () => { + getKibanaTranslationFilesMock.mockResolvedValue([]); + + const pluginPaths = ['/pathA', '/pathB']; + await service.setup({ pluginPaths }); + + expect(getKibanaTranslationFilesMock).toHaveBeenCalledTimes(1); + expect(getKibanaTranslationFilesMock).toHaveBeenCalledWith('en', pluginPaths); + }); + + it('calls `initTranslations` with the correct parameters', async () => { + const translationFiles = ['/path/to/file', 'path/to/another/file']; + getKibanaTranslationFilesMock.mockResolvedValue(translationFiles); + + await service.setup({ pluginPaths: [] }); + + expect(initTranslationsMock).toHaveBeenCalledTimes(1); + expect(initTranslationsMock).toHaveBeenCalledWith('en', translationFiles); + }); + + it('returns accessors for locale and translation files', async () => { + const translationFiles = ['/path/to/file', 'path/to/another/file']; + getKibanaTranslationFilesMock.mockResolvedValue(translationFiles); + + const { getLocale, getTranslationFiles } = await service.setup({ pluginPaths: [] }); + + expect(getLocale()).toEqual('en'); + expect(getTranslationFiles()).toEqual(translationFiles); + }); + }); +}); diff --git a/src/core/server/i18n/i18n_service.ts b/src/core/server/i18n/i18n_service.ts new file mode 100644 index 0000000000000..fd32dd7fdd6ef --- /dev/null +++ b/src/core/server/i18n/i18n_service.ts @@ -0,0 +1,75 @@ +/* + * 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 { take } from 'rxjs/operators'; +import { Logger } from '../logging'; +import { IConfigService } from '../config'; +import { CoreContext } from '../core_context'; +import { config as i18nConfigDef, I18nConfigType } from './i18n_config'; +import { getKibanaTranslationFiles } from './get_kibana_translation_files'; +import { initTranslations } from './init_translations'; + +interface SetupDeps { + pluginPaths: string[]; +} + +/** + * @public + */ +export interface I18nServiceSetup { + /** + * Return the locale currently in use. + */ + getLocale(): string; + + /** + * Return the absolute paths to translation files currently in use. + */ + getTranslationFiles(): string[]; +} + +export class I18nService { + private readonly log: Logger; + private readonly configService: IConfigService; + + constructor(coreContext: CoreContext) { + this.log = coreContext.logger.get('i18n'); + this.configService = coreContext.configService; + } + + public async setup({ pluginPaths }: SetupDeps): Promise { + const i18nConfig = await this.configService + .atPath(i18nConfigDef.path) + .pipe(take(1)) + .toPromise(); + + const locale = i18nConfig.locale; + this.log.debug(`Using locale: ${locale}`); + + const translationFiles = await getKibanaTranslationFiles(locale, pluginPaths); + + this.log.debug(`Using translation files: [${translationFiles.join(', ')}]`); + await initTranslations(locale, translationFiles); + + return { + getLocale: () => locale, + getTranslationFiles: () => translationFiles, + }; + } +} diff --git a/src/legacy/server/i18n/index.ts b/src/core/server/i18n/index.ts similarity index 86% rename from src/legacy/server/i18n/index.ts rename to src/core/server/i18n/index.ts index a7ef49f44532c..2db3fdeb405d9 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/core/server/i18n/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { i18nMixin } from './i18n_mixin'; +export { config, I18nConfigType } from './i18n_config'; +export { I18nService, I18nServiceSetup } from './i18n_service'; diff --git a/src/core/server/i18n/init_translations.ts b/src/core/server/i18n/init_translations.ts new file mode 100644 index 0000000000000..94e6d41019ad2 --- /dev/null +++ b/src/core/server/i18n/init_translations.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 { i18n, i18nLoader } from '@kbn/i18n'; + +export const initTranslations = async (locale: string, translationFiles: string[]) => { + i18nLoader.registerTranslationFiles(translationFiles); + const translations = await i18nLoader.getTranslationsByLocale(locale); + i18n.init( + Object.freeze({ + locale, + ...translations, + }) + ); +}; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0adda4770639d..7b19c3a686757 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -64,6 +64,7 @@ import { MetricsServiceSetup, MetricsServiceStart } from './metrics'; import { StatusServiceSetup } from './status'; import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; +import { I18nServiceSetup } from './i18n'; // Because of #79265 we need to explicity import, then export these types for // scripts/telemetry_check.js to work as expected @@ -336,6 +337,8 @@ export { MetricsServiceStart, } from './metrics'; +export { I18nServiceSetup } from './i18n'; + export { AppCategory } from '../types'; export { DEFAULT_APP_CATEGORIES } from '../utils'; @@ -421,6 +424,8 @@ export interface CoreSetup = KbnServer as any; @@ -75,6 +76,7 @@ beforeEach(() => { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, + i18n: i18nServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), http: { ...httpServiceMock.createInternalSetupContract(), diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index c42771179aba2..3111c8daf7981 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -251,6 +251,7 @@ export class LegacyService implements CoreService { csp: setupDeps.core.http.csp, getServerInfo: setupDeps.core.http.getServerInfo, }, + i18n: setupDeps.core.i18n, logging: { configure: (config$) => setupDeps.core.logging.configure([], config$), }, diff --git a/src/core/server/metrics/metrics_service.mock.ts b/src/core/server/metrics/metrics_service.mock.ts index 0d9e9af39317c..6faccc9c9da56 100644 --- a/src/core/server/metrics/metrics_service.mock.ts +++ b/src/core/server/metrics/metrics_service.mock.ts @@ -19,7 +19,7 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import { MetricsService } from './metrics_service'; +import type { MetricsService } from './metrics_service'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 8ca0c82219ed4..03a0ae2d6443a 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -38,6 +38,7 @@ import { metricsServiceMock } from './metrics/metrics_service.mock'; import { environmentServiceMock } from './environment/environment_service.mock'; import { statusServiceMock } from './status/status_service.mock'; import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; +import { i18nServiceMock } from './i18n/i18n_service.mock'; export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; @@ -57,6 +58,7 @@ export { statusServiceMock } from './status/status_service.mock'; export { contextServiceMock } from './context/context_service.mock'; export { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; +export { i18nServiceMock } from './i18n/i18n_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { @@ -136,6 +138,7 @@ function createCoreSetupMock({ context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, + i18n: i18nServiceMock.createSetupContract(), savedObjects: savedObjectsServiceMock.createInternalSetupContract(), status: statusServiceMock.createSetupContract(), uiSettings: uiSettingsMock, @@ -172,6 +175,7 @@ function createInternalCoreSetupMock() { savedObjects: savedObjectsServiceMock.createInternalSetupContract(), status: statusServiceMock.createInternalSetupContract(), environment: environmentServiceMock.createSetupContract(), + i18n: i18nServiceMock.createSetupContract(), httpResources: httpResourcesMock.createSetupContract(), rendering: renderingMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 22e79741e854c..3b2634ddbe315 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -176,6 +176,7 @@ export function createPluginSetupContext( csp: deps.http.csp, getServerInfo: deps.http.getServerInfo, }, + i18n: deps.i18n, logging: { configure: (config$) => deps.logging.configure(['plugins', plugin.name], config$), }, diff --git a/src/core/server/plugins/plugins_service.mock.ts b/src/core/server/plugins/plugins_service.mock.ts index 14d6de889dd42..3ea8badb0f450 100644 --- a/src/core/server/plugins/plugins_service.mock.ts +++ b/src/core/server/plugins/plugins_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { PluginsService, PluginsServiceSetup } from './plugins_service'; +import type { PluginsService, PluginsServiceSetup } from './plugins_service'; type PluginsServiceMock = jest.Mocked>; diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 64a382e539fb0..02b82c17ed4fc 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -127,6 +127,7 @@ async function testSetup(options: { isDevClusterMaster?: boolean } = {}) { [mockPluginSystem] = MockPluginsSystem.mock.instances as any; mockPluginSystem.uiPlugins.mockReturnValue(new Map()); + mockPluginSystem.getPlugins.mockReturnValue([]); environmentSetup = environmentServiceMock.createSetupContract(); } @@ -469,6 +470,22 @@ describe('PluginsService', () => { deprecationProvider ); }); + + it('returns the paths of the plugins', async () => { + const pluginA = createPlugin('A', { path: '/plugin-A-path', configPath: 'pathA' }); + const pluginB = createPlugin('B', { path: '/plugin-B-path', configPath: 'pathB' }); + + mockDiscover.mockReturnValue({ + error$: from([]), + plugin$: from([]), + }); + + mockPluginSystem.getPlugins.mockReturnValue([pluginA, pluginB]); + + const { pluginPaths } = await pluginsService.discover({ environment: environmentSetup }); + + expect(pluginPaths).toEqual(['/plugin-A-path', '/plugin-B-path']); + }); }); describe('#generateUiPluginsConfigs()', () => { @@ -633,6 +650,7 @@ describe('PluginService when isDevClusterMaster is true', () => { await expect(pluginsService.discover({ environment: environmentSetup })).resolves .toMatchInlineSnapshot(` Object { + "pluginPaths": Array [], "pluginTree": undefined, "uiPlugins": Object { "browserConfigs": Map {}, diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index a1062bde7765f..5967e6d5358de 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -118,6 +118,7 @@ export class PluginsService implements CoreService plugin.path), uiPlugins: { internal: this.uiPluginInternalInfo, public: uiPlugins, diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index ae9267ca5cf60..89a3697ebe9cd 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -92,6 +92,15 @@ test('can be setup even without plugins', async () => { expect(pluginsSetup.size).toBe(0); }); +test('getPlugins returns the list of plugins', () => { + const pluginA = createPlugin('plugin-a'); + const pluginB = createPlugin('plugin-b'); + pluginsSystem.addPlugin(pluginA); + pluginsSystem.addPlugin(pluginB); + + expect(pluginsSystem.getPlugins()).toEqual([pluginA, pluginB]); +}); + test('getPluginDependencies returns dependency tree of symbols', () => { pluginsSystem.addPlugin(createPlugin('plugin-a', { required: ['no-dep'] })); pluginsSystem.addPlugin( diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index 72d2cfe158b37..23dc77b7bf673 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -42,6 +42,10 @@ export class PluginsSystem { this.plugins.set(plugin.name, plugin); } + public getPlugins() { + return [...this.plugins.values()]; + } + /** * @returns a ReadonlyMap of each plugin and an Array of its available dependencies * @internal diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index 01d084f9ae53c..00e617de82542 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -17,8 +17,8 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { RenderingService as Service } from '../rendering_service'; -import { InternalRenderingServiceSetup } from '../types'; +import type { RenderingService as Service } from '../rendering_service'; +import type { InternalRenderingServiceSetup } from '../types'; import { mockRenderingServiceParams } from './params'; type IRenderingService = PublicMethodsOf; diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index c56cdabf6e4cd..85dbf4b5e8c6a 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -19,8 +19,7 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; - -import { +import type { SavedObjectsService, InternalSavedObjectsServiceSetup, InternalSavedObjectsServiceStart, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 52500673f7f31..88d7fecbcf502 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -480,6 +480,8 @@ export interface CoreSetup HttpServerInfo; } +// @public (undocumented) +export interface I18nServiceSetup { + getLocale(): string; + getTranslationFiles(): string[]; +} + // @public export type IBasePath = Pick; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index fe299c6d11675..d2d6990dc5451 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -100,3 +100,9 @@ export const mockLoggingService = loggingServiceMock.create(); jest.doMock('./logging/logging_service', () => ({ LoggingService: jest.fn(() => mockLoggingService), })); + +import { i18nServiceMock } from './i18n/i18n_service.mock'; +export const mockI18nService = i18nServiceMock.create(); +jest.doMock('./i18n/i18n_service', () => ({ + I18nService: jest.fn(() => mockI18nService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 78703ceeec7ae..0c7ebbcb527ec 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -31,6 +31,7 @@ import { mockMetricsService, mockStatusService, mockLoggingService, + mockI18nService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -49,6 +50,7 @@ beforeEach(() => { mockConfigService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); mockPluginsService.discover.mockResolvedValue({ pluginTree: { asOpaqueIds: new Map(), asNames: new Map() }, + pluginPaths: [], uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, }); }); @@ -70,6 +72,7 @@ test('sets up services on "setup"', async () => { expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); expect(mockLoggingService.setup).not.toHaveBeenCalled(); + expect(mockI18nService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -83,6 +86,7 @@ test('sets up services on "setup"', async () => { expect(mockMetricsService.setup).toHaveBeenCalledTimes(1); expect(mockStatusService.setup).toHaveBeenCalledTimes(1); expect(mockLoggingService.setup).toHaveBeenCalledTimes(1); + expect(mockI18nService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -96,6 +100,7 @@ test('injects legacy dependency to context#setup()', async () => { ]); mockPluginsService.discover.mockResolvedValue({ pluginTree: { asOpaqueIds: pluginDependencies, asNames: new Map() }, + pluginPaths: [], uiPlugins: { internal: new Map(), public: new Map(), browserConfigs: new Map() }, }); @@ -185,6 +190,7 @@ test(`doesn't setup core services if config validation fails`, async () => { expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); expect(mockLoggingService.setup).not.toHaveBeenCalled(); + expect(mockI18nService.setup).not.toHaveBeenCalled(); }); test(`doesn't setup core services if legacy config validation fails`, async () => { @@ -207,6 +213,7 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); expect(mockLoggingService.setup).not.toHaveBeenCalled(); + expect(mockI18nService.setup).not.toHaveBeenCalled(); }); test(`doesn't validate config if env.isDevClusterMaster is true`, async () => { diff --git a/src/core/server/server.ts b/src/core/server/server.ts index eaa03d11cab98..55ed88e55a9f5 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -16,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import apm from 'elastic-apm-node'; import { config as pathConfig } from '@kbn/utils'; import { mapToObject } from '@kbn/std'; import { ConfigService, Env, RawConfigurationProvider, coreDeprecationProvider } from './config'; import { CoreApp } from './core_app'; +import { I18nService } from './i18n'; import { ElasticsearchService } from './elasticsearch'; import { HttpService } from './http'; import { HttpResourcesService } from './http_resources'; @@ -29,10 +31,11 @@ import { LegacyService, ensureValidConfiguration } from './legacy'; import { Logger, LoggerFactory, LoggingService, ILoggingSystem } from './logging'; import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; -import { SavedObjectsService } from '../server/saved_objects'; +import { SavedObjectsService } from './saved_objects'; import { MetricsService, opsConfig } from './metrics'; import { CapabilitiesService } from './capabilities'; import { EnvironmentService, config as pidConfig } from './environment'; +// do not try to shorten the import to `./status`, it will break server test mocking import { StatusService } from './status/status_service'; import { config as cspConfig } from './csp'; @@ -44,6 +47,7 @@ import { config as kibanaConfig } from './kibana_config'; import { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects'; import { config as uiSettingsConfig } from './ui_settings'; import { config as statusConfig } from './status'; +import { config as i18nConfig } from './i18n'; import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from './internal_types'; @@ -72,6 +76,7 @@ export class Server { private readonly logging: LoggingService; private readonly coreApp: CoreApp; private readonly coreUsageData: CoreUsageDataService; + private readonly i18n: I18nService; #pluginsInitialized?: boolean; private coreStart?: InternalCoreStart; @@ -103,6 +108,7 @@ export class Server { this.httpResources = new HttpResourcesService(core); this.logging = new LoggingService(core); this.coreUsageData = new CoreUsageDataService(core); + this.i18n = new I18nService(core); } public async setup() { @@ -112,7 +118,7 @@ export class Server { const environmentSetup = await this.environment.setup(); // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. - const { pluginTree, uiPlugins } = await this.plugins.discover({ + const { pluginTree, pluginPaths, uiPlugins } = await this.plugins.discover({ environment: environmentSetup, }); const legacyConfigSetup = await this.legacy.setupLegacyConfig(); @@ -125,6 +131,9 @@ export class Server { await ensureValidConfiguration(this.configService, legacyConfigSetup); } + // setup i18n prior to any other service, to have translations ready + const i18nServiceSetup = await this.i18n.setup({ pluginPaths }); + const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: // 1) Can access context from any KP plugin @@ -190,6 +199,7 @@ export class Server { elasticsearch: elasticsearchServiceSetup, environment: environmentSetup, http: httpSetup, + i18n: i18nServiceSetup, savedObjects: savedObjectsSetup, status: statusSetup, uiSettings: uiSettingsSetup, @@ -302,6 +312,7 @@ export class Server { opsConfig, statusConfig, pidConfig, + i18nConfig, ]; this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider); diff --git a/src/core/server/status/status_service.mock.ts b/src/core/server/status/status_service.mock.ts index 0ee2d03229a78..42892ddbb490c 100644 --- a/src/core/server/status/status_service.mock.ts +++ b/src/core/server/status/status_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { StatusService } from './status_service'; +import type { StatusService } from './status_service'; import { InternalStatusServiceSetup, StatusServiceSetup, diff --git a/src/core/server/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts index b1ed0dd188cde..818e9b43889e3 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -22,7 +22,7 @@ import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart, } from './types'; -import { UiSettingsService } from './ui_settings_service'; +import type { UiSettingsService } from './ui_settings_service'; const createClientMock = () => { const mocked: jest.Mocked = { diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index e1f03b8a08847..39df3990ff2ff 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -128,21 +128,12 @@ export default () => cGroupOverrides: HANDLED_IN_NEW_PLATFORM, }).default(), - // still used by the legacy i18n mixin - plugins: Joi.object({ - paths: Joi.array().items(Joi.string()).default([]), - scanDirs: Joi.array().items(Joi.string()).default([]), - initialize: Joi.boolean().default(true), - }).default(), - + plugins: HANDLED_IN_NEW_PLATFORM, path: HANDLED_IN_NEW_PLATFORM, stats: HANDLED_IN_NEW_PLATFORM, status: HANDLED_IN_NEW_PLATFORM, map: HANDLED_IN_NEW_PLATFORM, - - i18n: Joi.object({ - locale: Joi.string().default('en'), - }).default(), + i18n: HANDLED_IN_NEW_PLATFORM, // temporarily moved here from the (now deleted) kibana legacy plugin kibana: Joi.object({ diff --git a/src/legacy/server/i18n/get_kibana_translation_paths.test.ts b/src/legacy/server/i18n/get_kibana_translation_paths.test.ts deleted file mode 100644 index 0f202c4d433c0..0000000000000 --- a/src/legacy/server/i18n/get_kibana_translation_paths.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { I18N_RC } from './constants'; -import { fromRoot } from '../../../core/server/utils'; - -jest.mock('./get_translation_paths', () => ({ getTranslationPaths: jest.fn() })); -import { getKibanaTranslationPaths } from './get_kibana_translation_paths'; -import { getTranslationPaths as mockGetTranslationPaths } from './get_translation_paths'; - -describe('getKibanaTranslationPaths', () => { - const mockConfig = { get: jest.fn() }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('calls getTranslationPaths against kibana root and kibana-extra', async () => { - mockConfig.get.mockReturnValue([]); - await getKibanaTranslationPaths(mockConfig); - expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(1, { - cwd: fromRoot('.'), - glob: `*/${I18N_RC}`, - }); - - expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(2, { - cwd: fromRoot('../kibana-extra'), - glob: `*/${I18N_RC}`, - }); - }); - - it('calls getTranslationPaths for each config returned in plugin.paths and plugins.scanDirs', async () => { - mockConfig.get.mockReturnValueOnce(['a', 'b']).mockReturnValueOnce(['c']); - await getKibanaTranslationPaths(mockConfig); - expect(mockConfig.get).toHaveBeenNthCalledWith(1, 'plugins.paths'); - expect(mockConfig.get).toHaveBeenNthCalledWith(2, 'plugins.scanDirs'); - - expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(2, { cwd: 'a', glob: I18N_RC }); - expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(3, { cwd: 'b', glob: I18N_RC }); - expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(4, { cwd: 'c', glob: `*/${I18N_RC}` }); - }); -}); diff --git a/src/legacy/server/i18n/i18n_mixin.ts b/src/legacy/server/i18n/i18n_mixin.ts deleted file mode 100644 index 0b3879073c164..0000000000000 --- a/src/legacy/server/i18n/i18n_mixin.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { i18n, i18nLoader } from '@kbn/i18n'; -import { basename } from 'path'; -import { Server } from '@hapi/hapi'; -import type { UsageCollectionSetup } from '../../../plugins/usage_collection/server'; -import { getKibanaTranslationPaths } from './get_kibana_translation_paths'; -import KbnServer, { KibanaConfig } from '../kbn_server'; -import { registerLocalizationUsageCollector } from './localization'; - -export async function i18nMixin( - kbnServer: KbnServer, - server: Server, - config: Pick -) { - const locale = config.get('i18n.locale') as string; - - const translationPaths = await getKibanaTranslationPaths(config); - - const currentTranslationPaths = ([] as string[]) - .concat(...translationPaths) - .filter((translationPath) => basename(translationPath, '.json') === locale); - i18nLoader.registerTranslationFiles(currentTranslationPaths); - - const translations = await i18nLoader.getTranslationsByLocale(locale); - i18n.init( - Object.freeze({ - locale, - ...translations, - }) - ); - - const getTranslationsFilePaths = () => currentTranslationPaths; - - server.decorate('server', 'getTranslationsFilePaths', getTranslationsFilePaths); - - if (kbnServer.newPlatform.setup.plugins.usageCollection) { - const { usageCollection } = kbnServer.newPlatform.setup.plugins as { - usageCollection: UsageCollectionSetup; - }; - registerLocalizationUsageCollector(usageCollection, { - getLocale: () => config.get('i18n.locale') as string, - getTranslationsFilePaths, - }); - } -} diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index e29563a7c6266..013da35d2acb7 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -31,7 +31,6 @@ import warningsMixin from './warnings'; import configCompleteMixin from './config/complete'; import { optimizeMixin } from '../../optimize'; import { uiMixin } from '../ui'; -import { i18nMixin } from './i18n'; /** * @typedef {import('./kbn_server').KibanaConfig} KibanaConfig @@ -82,9 +81,6 @@ export default class KbnServer { loggingMixin, warningsMixin, - // scan translations dirs, register locale files and initialize i18n engine. - i18nMixin, - // tell the config we are done loading plugins configCompleteMixin, diff --git a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap index c479562795512..c782ce9c8cc84 100644 --- a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap +++ b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap @@ -13,3 +13,5 @@ exports[`kibana_usage_collection Runs the setup method without issues 5`] = `fal exports[`kibana_usage_collection Runs the setup method without issues 6`] = `true`; exports[`kibana_usage_collection Runs the setup method without issues 7`] = `false`; + +exports[`kibana_usage_collection Runs the setup method without issues 8`] = `true`; diff --git a/src/plugins/kibana_usage_collection/server/collectors/index.ts b/src/plugins/kibana_usage_collection/server/collectors/index.ts index 2408dc84c2e56..f3b7d8ca5eea0 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/index.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/index.ts @@ -24,3 +24,4 @@ export { registerKibanaUsageCollector } from './kibana'; export { registerOpsStatsCollector } from './ops_stats'; export { registerCspCollector } from './csp'; export { registerCoreUsageCollector } from './core'; +export { registerLocalizationUsageCollector } from './localization'; diff --git a/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.test.mocks.ts similarity index 100% rename from src/legacy/server/i18n/localization/file_integrity.test.mocks.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.test.mocks.ts diff --git a/src/legacy/server/i18n/localization/file_integrity.test.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.test.ts similarity index 100% rename from src/legacy/server/i18n/localization/file_integrity.test.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.test.ts diff --git a/src/legacy/server/i18n/localization/file_integrity.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.ts similarity index 100% rename from src/legacy/server/i18n/localization/file_integrity.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/file_integrity.ts diff --git a/src/legacy/server/i18n/localization/index.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/index.ts similarity index 100% rename from src/legacy/server/i18n/localization/index.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/index.ts diff --git a/src/legacy/server/i18n/localization/telemetry_localization_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/telemetry_localization_collector.test.ts similarity index 100% rename from src/legacy/server/i18n/localization/telemetry_localization_collector.test.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/telemetry_localization_collector.test.ts diff --git a/src/legacy/server/i18n/localization/telemetry_localization_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/localization/telemetry_localization_collector.ts similarity index 82% rename from src/legacy/server/i18n/localization/telemetry_localization_collector.ts rename to src/plugins/kibana_usage_collection/server/collectors/localization/telemetry_localization_collector.ts index fb837f5ae28df..f2b00bd629a07 100644 --- a/src/legacy/server/i18n/localization/telemetry_localization_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/localization/telemetry_localization_collector.ts @@ -20,6 +20,7 @@ import { i18nLoader } from '@kbn/i18n'; import { size } from 'lodash'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { I18nServiceSetup } from '../../../../../core/server'; import { getIntegrityHashes, Integrities } from './file_integrity'; export interface UsageStats { @@ -28,11 +29,6 @@ export interface UsageStats { labelsCount?: number; } -export interface LocalizationUsageCollectorHelpers { - getLocale: () => string; - getTranslationsFilePaths: () => string[]; -} - export async function getTranslationCount( loader: typeof i18nLoader, locale: string @@ -41,13 +37,10 @@ export async function getTranslationCount( return size(translations.messages); } -export function createCollectorFetch({ - getLocale, - getTranslationsFilePaths, -}: LocalizationUsageCollectorHelpers) { +export function createCollectorFetch({ getLocale, getTranslationFiles }: I18nServiceSetup) { return async function fetchUsageStats(): Promise { const locale = getLocale(); - const translationFilePaths: string[] = getTranslationsFilePaths(); + const translationFilePaths: string[] = getTranslationFiles(); const [labelsCount, integrities] = await Promise.all([ getTranslationCount(i18nLoader, locale), @@ -62,15 +55,14 @@ export function createCollectorFetch({ }; } -// TODO: Migrate out of the Legacy dir export function registerLocalizationUsageCollector( usageCollection: UsageCollectionSetup, - helpers: LocalizationUsageCollectorHelpers + i18n: I18nServiceSetup ) { const collector = usageCollection.makeUsageCollector({ type: 'localization', isReady: () => true, - fetch: createCollectorFetch(helpers), + fetch: createCollectorFetch(i18n), schema: { locale: { type: 'keyword' }, integrities: { DYNAMIC_KEY: { type: 'text' } }, diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index 198fdbb7a8703..16cb620351aaa 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -41,6 +41,7 @@ import { registerUiMetricUsageCollector, registerCspCollector, registerCoreUsageCollector, + registerLocalizationUsageCollector, } from './collectors'; interface KibanaUsageCollectionPluginsDepsSetup { @@ -104,5 +105,6 @@ export class KibanaUsageCollectionPlugin implements Plugin { ); registerCspCollector(usageCollection, coreSetup.http); registerCoreUsageCollector(usageCollection, getCoreUsageDataService); + registerLocalizationUsageCollector(usageCollection, coreSetup.i18n); } } diff --git a/src/plugins/telemetry/schema/legacy_plugins.json b/src/plugins/telemetry/schema/legacy_plugins.json index 1a7c0ccb15082..d5b0514b64918 100644 --- a/src/plugins/telemetry/schema/legacy_plugins.json +++ b/src/plugins/telemetry/schema/legacy_plugins.json @@ -1,21 +1,3 @@ { - "properties": { - "localization": { - "properties": { - "locale": { - "type": "keyword" - }, - "integrities": { - "properties": { - "DYNAMIC_KEY": { - "type": "text" - } - } - }, - "labelsCount": { - "type": "long" - } - } - } - } + "properties": {} } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index c840cbe8fc94d..a1eae69ffaed0 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -1581,6 +1581,23 @@ } } }, + "localization": { + "properties": { + "locale": { + "type": "keyword" + }, + "integrities": { + "properties": { + "DYNAMIC_KEY": { + "type": "text" + } + } + }, + "labelsCount": { + "type": "long" + } + } + }, "stack_management": { "properties": { "visualize:enableLabs": { From e909cee7f1a9206dbd3822fe92b36890b29dec6c Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 10 Nov 2020 09:49:20 -0500 Subject: [PATCH 51/87] [Maps] add on-prem EMS config (#82525) Adds.a new `map.emsUrl` setting. User can configure this to the location of a local on-prem installation of EMS. For this setting to take effect, the cluster must be configured with an enterprise license. --- .../maps_legacy/common/ems_defaults.ts | 25 ++ src/plugins/maps_legacy/common/index.ts | 20 ++ src/plugins/maps_legacy/config.ts | 19 +- src/plugins/maps_legacy/kibana.json | 1 + src/plugins/maps_legacy/public/index.ts | 1 + .../public/map/service_settings.js | 2 +- src/plugins/maps_legacy/server/index.ts | 1 + .../plugins/maps/common/ems_settings.test.ts | 221 ++++++++++++++++++ x-pack/plugins/maps/common/ems_settings.ts | 91 ++++++++ .../ems_boundaries_layer_wizard.tsx | 5 +- .../ems_base_map_layer_wizard.tsx | 5 +- .../components/ems_unavailable_message.tsx | 7 +- x-pack/plugins/maps/public/kibana_services.ts | 17 +- .../plugins/maps/public/licensed_features.ts | 8 + x-pack/plugins/maps/public/meta.test.js | 42 +++- x-pack/plugins/maps/public/meta.ts | 31 ++- x-pack/plugins/maps/public/plugin.ts | 11 +- .../bootstrap/get_initial_layers.test.js | 18 +- .../routing/bootstrap/get_initial_layers.ts | 4 +- x-pack/plugins/maps/server/plugin.ts | 20 +- x-pack/plugins/maps/server/routes.js | 80 ++++--- 21 files changed, 532 insertions(+), 97 deletions(-) create mode 100644 src/plugins/maps_legacy/common/ems_defaults.ts create mode 100644 src/plugins/maps_legacy/common/index.ts create mode 100644 x-pack/plugins/maps/common/ems_settings.test.ts create mode 100644 x-pack/plugins/maps/common/ems_settings.ts diff --git a/src/plugins/maps_legacy/common/ems_defaults.ts b/src/plugins/maps_legacy/common/ems_defaults.ts new file mode 100644 index 0000000000000..583dca1dbf036 --- /dev/null +++ b/src/plugins/maps_legacy/common/ems_defaults.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. + */ + +// Default config for the elastic hosted EMS endpoints +export const DEFAULT_EMS_FILE_API_URL = 'https://vector.maps.elastic.co'; +export const DEFAULT_EMS_TILE_API_URL = 'https://tiles.maps.elastic.co'; +export const DEFAULT_EMS_LANDING_PAGE_URL = 'https://maps.elastic.co/v7.10'; +export const DEFAULT_EMS_FONT_LIBRARY_URL = + 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'; diff --git a/src/plugins/maps_legacy/common/index.ts b/src/plugins/maps_legacy/common/index.ts new file mode 100644 index 0000000000000..12148ec1ec6b8 --- /dev/null +++ b/src/plugins/maps_legacy/common/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 * from './ems_defaults'; diff --git a/src/plugins/maps_legacy/config.ts b/src/plugins/maps_legacy/config.ts index f49d56dedd45f..68595944e68b3 100644 --- a/src/plugins/maps_legacy/config.ts +++ b/src/plugins/maps_legacy/config.ts @@ -21,18 +21,29 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { configSchema as tilemapSchema } from '../tile_map/config'; import { configSchema as regionmapSchema } from '../region_map/config'; +import { + DEFAULT_EMS_FONT_LIBRARY_URL, + DEFAULT_EMS_LANDING_PAGE_URL, + DEFAULT_EMS_TILE_API_URL, + DEFAULT_EMS_FILE_API_URL, +} from './common/ems_defaults'; + export const configSchema = schema.object({ includeElasticMapsService: schema.boolean({ defaultValue: true }), proxyElasticMapsServiceInMaps: schema.boolean({ defaultValue: false }), tilemap: tilemapSchema, regionmap: regionmapSchema, manifestServiceUrl: schema.string({ defaultValue: '' }), - emsFileApiUrl: schema.string({ defaultValue: 'https://vector.maps.elastic.co' }), - emsTileApiUrl: schema.string({ defaultValue: 'https://tiles.maps.elastic.co' }), - emsLandingPageUrl: schema.string({ defaultValue: 'https://maps.elastic.co/v7.10' }), + + emsUrl: schema.string({ defaultValue: '' }), + + emsFileApiUrl: schema.string({ defaultValue: DEFAULT_EMS_FILE_API_URL }), + emsTileApiUrl: schema.string({ defaultValue: DEFAULT_EMS_TILE_API_URL }), + emsLandingPageUrl: schema.string({ defaultValue: DEFAULT_EMS_LANDING_PAGE_URL }), emsFontLibraryUrl: schema.string({ - defaultValue: 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf', + defaultValue: DEFAULT_EMS_FONT_LIBRARY_URL, }), + emsTileLayerId: schema.object({ bright: schema.string({ defaultValue: 'road_map' }), desaturated: schema.string({ defaultValue: 'road_map_desaturated' }), diff --git a/src/plugins/maps_legacy/kibana.json b/src/plugins/maps_legacy/kibana.json index d9bf33e661368..1499b3de446b5 100644 --- a/src/plugins/maps_legacy/kibana.json +++ b/src/plugins/maps_legacy/kibana.json @@ -5,5 +5,6 @@ "configPath": ["map"], "ui": true, "server": true, + "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "charts"] } diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index fe5338b890ec8..2654ded907cce 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -59,6 +59,7 @@ export { mapTooltipProvider, }; +export * from '../common'; export * from './common/types'; export { ORIGIN } from './common/constants/origin'; diff --git a/src/plugins/maps_legacy/public/map/service_settings.js b/src/plugins/maps_legacy/public/map/service_settings.js index 833304378402a..7a00456b89a92 100644 --- a/src/plugins/maps_legacy/public/map/service_settings.js +++ b/src/plugins/maps_legacy/public/map/service_settings.js @@ -128,7 +128,7 @@ export class ServiceSettings { allServices.push(tmsService); } - if (this._mapConfig.includeElasticMapsService) { + if (this._mapConfig.includeElasticMapsService && !this._mapConfig.emsUrl) { const servicesFromManifest = await this._emsClient.getTMSServices(); const strippedServiceFromManifest = await Promise.all( servicesFromManifest diff --git a/src/plugins/maps_legacy/server/index.ts b/src/plugins/maps_legacy/server/index.ts index 665b3b8986ef0..ba37df60f9357 100644 --- a/src/plugins/maps_legacy/server/index.ts +++ b/src/plugins/maps_legacy/server/index.ts @@ -30,6 +30,7 @@ export const config: PluginConfigDescriptor = { tilemap: true, regionmap: true, manifestServiceUrl: true, + emsUrl: true, emsFileApiUrl: true, emsTileApiUrl: true, emsLandingPageUrl: true, diff --git a/x-pack/plugins/maps/common/ems_settings.test.ts b/x-pack/plugins/maps/common/ems_settings.test.ts new file mode 100644 index 0000000000000..69ae7069129cb --- /dev/null +++ b/x-pack/plugins/maps/common/ems_settings.test.ts @@ -0,0 +1,221 @@ +/* + * 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 { EMSSettings, IEMSConfig } from './ems_settings'; +import { + DEFAULT_EMS_FILE_API_URL, + DEFAULT_EMS_FONT_LIBRARY_URL, + DEFAULT_EMS_LANDING_PAGE_URL, + DEFAULT_EMS_TILE_API_URL, +} from '../../../../src/plugins/maps_legacy/common'; + +const IS_ENTERPRISE_PLUS = () => true; + +describe('EMSSettings', () => { + const mockConfig: IEMSConfig = { + includeElasticMapsService: true, + proxyElasticMapsServiceInMaps: false, + emsUrl: '', + emsFileApiUrl: DEFAULT_EMS_FILE_API_URL, + emsTileApiUrl: DEFAULT_EMS_TILE_API_URL, + emsLandingPageUrl: DEFAULT_EMS_LANDING_PAGE_URL, + emsFontLibraryUrl: DEFAULT_EMS_FONT_LIBRARY_URL, + isEMSEnabled: true, + }; + + describe('isEMSEnabled/isOnPrem', () => { + test('should validate defaults', () => { + const emsSettings = new EMSSettings(mockConfig, IS_ENTERPRISE_PLUS); + expect(emsSettings.isEMSEnabled()).toBe(true); + expect(emsSettings.isOnPrem()).toBe(false); + }); + + test('should validate if on-prem is turned on', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.isEMSEnabled()).toBe(true); + expect(emsSettings.isOnPrem()).toBe(true); + }); + + test('should not validate if ems turned off', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + includeElasticMapsService: false, + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.isEMSEnabled()).toBe(false); + expect(emsSettings.isOnPrem()).toBe(false); + }); + + test('should work if ems is turned off, but on-prem is turned on', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + includeElasticMapsService: false, + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.isEMSEnabled()).toBe(true); + expect(emsSettings.isOnPrem()).toBe(true); + }); + + describe('when license is turned off', () => { + test('should not be enabled', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + }, + }, + () => false + ); + expect(emsSettings.isEMSEnabled()).toBe(false); + expect(emsSettings.isOnPrem()).toBe(true); + }); + }); + }); + + describe('emsUrl setting', () => { + describe('when emsUrl is not set', () => { + test('should respect defaults', () => { + const emsSettings = new EMSSettings(mockConfig, IS_ENTERPRISE_PLUS); + expect(emsSettings.getEMSFileApiUrl()).toBe(DEFAULT_EMS_FILE_API_URL); + expect(emsSettings.getEMSTileApiUrl()).toBe(DEFAULT_EMS_TILE_API_URL); + expect(emsSettings.getEMSFontLibraryUrl()).toBe(DEFAULT_EMS_FONT_LIBRARY_URL); + expect(emsSettings.getEMSLandingPageUrl()).toBe(DEFAULT_EMS_LANDING_PAGE_URL); + }); + test('should apply overrides', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsFileApiUrl: 'https://file.foobar', + emsTileApiUrl: 'https://tile.foobar', + emsFontLibraryUrl: 'https://tile.foobar/font', + emsLandingPageUrl: 'https://maps.foobar/v7.666', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://file.foobar'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://tile.foobar'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe('https://tile.foobar/font'); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://maps.foobar/v7.666'); + }); + }); + + describe('when emsUrl is set', () => { + test('should override defaults', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://localhost:8080/file'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://localhost:8080/tile'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe( + 'https://localhost:8080/tile/fonts/{fontstack}/{range}.pbf' + ); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://localhost:8080/maps'); + }); + + describe('internal settings overrides (the below behavior is not publically supported, but aids internal debugging use-cases)', () => { + test(`should override internal emsFileApiUrl`, () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + emsFileApiUrl: 'https://file.foobar', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://file.foobar'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://localhost:8080/tile'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe( + 'https://localhost:8080/tile/fonts/{fontstack}/{range}.pbf' + ); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://localhost:8080/maps'); + }); + + test(`should override internal emsTileApiUrl`, () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + emsTileApiUrl: 'https://tile.foobar', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://localhost:8080/file'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://tile.foobar'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe( + 'https://localhost:8080/tile/fonts/{fontstack}/{range}.pbf' + ); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://localhost:8080/maps'); + }); + + test('should override internal emsFontLibraryUrl', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + emsFontLibraryUrl: 'https://maps.foobar/fonts', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://localhost:8080/file'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://localhost:8080/tile'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe('https://maps.foobar/fonts'); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://localhost:8080/maps'); + }); + + test('should override internal emsLandingPageUrl', () => { + const emsSettings = new EMSSettings( + { + ...mockConfig, + ...{ + emsUrl: 'https://localhost:8080', + emsLandingPageUrl: 'https://maps.foobar', + }, + }, + IS_ENTERPRISE_PLUS + ); + expect(emsSettings.getEMSFileApiUrl()).toBe('https://localhost:8080/file'); + expect(emsSettings.getEMSTileApiUrl()).toBe('https://localhost:8080/tile'); + expect(emsSettings.getEMSFontLibraryUrl()).toBe( + 'https://localhost:8080/tile/fonts/{fontstack}/{range}.pbf' + ); + expect(emsSettings.getEMSLandingPageUrl()).toBe('https://maps.foobar'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/maps/common/ems_settings.ts b/x-pack/plugins/maps/common/ems_settings.ts new file mode 100644 index 0000000000000..0f4d211f0e963 --- /dev/null +++ b/x-pack/plugins/maps/common/ems_settings.ts @@ -0,0 +1,91 @@ +/* + * 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 { + DEFAULT_EMS_FILE_API_URL, + DEFAULT_EMS_FONT_LIBRARY_URL, + DEFAULT_EMS_LANDING_PAGE_URL, + DEFAULT_EMS_TILE_API_URL, +} from '../../../../src/plugins/maps_legacy/common'; + +export interface IEMSConfig { + emsUrl?: string; + includeElasticMapsService?: boolean; + proxyElasticMapsServiceInMaps?: boolean; + emsFileApiUrl?: string; + emsTileApiUrl?: string; + emsLandingPageUrl?: string; + emsFontLibraryUrl?: string; + isEMSEnabled?: boolean; +} + +export class EMSSettings { + private readonly _config: IEMSConfig; + private readonly _getIsEnterprisePlus: () => boolean; + + constructor(config: IEMSConfig, getIsEnterPrisePlus: () => boolean) { + this._config = config; + this._getIsEnterprisePlus = getIsEnterPrisePlus; + } + + _isEMSUrlSet() { + return !!this._config.emsUrl; + } + + _getEMSRoot() { + return this._config.emsUrl!.replace(/\/$/, ''); + } + + isOnPrem(): boolean { + return this._isEMSUrlSet(); + } + + isIncludeElasticMapsService() { + return !!this._config.includeElasticMapsService; + } + + isEMSEnabled(): boolean { + if (this._isEMSUrlSet()) { + return this._getIsEnterprisePlus(); + } + return this.isIncludeElasticMapsService(); + } + + getEMSFileApiUrl(): string { + if (this._config.emsFileApiUrl !== DEFAULT_EMS_FILE_API_URL || !this._isEMSUrlSet()) { + return this._config.emsFileApiUrl!; + } else { + return `${this._getEMSRoot()}/file`; + } + } + + isProxyElasticMapsServiceInMaps(): boolean { + return !!this._config.proxyElasticMapsServiceInMaps; + } + + getEMSTileApiUrl(): string { + if (this._config.emsTileApiUrl !== DEFAULT_EMS_TILE_API_URL || !this._isEMSUrlSet()) { + return this._config.emsTileApiUrl!; + } else { + return `${this._getEMSRoot()}/tile`; + } + } + getEMSLandingPageUrl(): string { + if (this._config.emsLandingPageUrl !== DEFAULT_EMS_LANDING_PAGE_URL || !this._isEMSUrlSet()) { + return this._config.emsLandingPageUrl!; + } else { + return `${this._getEMSRoot()}/maps`; + } + } + + getEMSFontLibraryUrl(): string { + if (this._config.emsFontLibraryUrl !== DEFAULT_EMS_FONT_LIBRARY_URL || !this._isEMSUrlSet()) { + return this._config.emsFontLibraryUrl!; + } else { + return `${this._getEMSRoot()}/tile/fonts/{fontstack}/{range}.pbf`; + } + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx index 8d4d57e524276..768bbd1d94700 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx @@ -11,14 +11,15 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re import { EMSFileCreateSourceEditor } from './create_source_editor'; import { EMSFileSource, sourceTitle } from './ems_file_source'; // @ts-ignore -import { getIsEmsEnabled } from '../../../kibana_services'; +import { getEMSSettings } from '../../../kibana_services'; import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const emsBoundariesLayerWizardConfig: LayerWizard = { categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: async () => { - return getIsEmsEnabled(); + const emsSettings = getEMSSettings(); + return emsSettings!.isEMSEnabled(); }, description: i18n.translate('xpack.maps.source.emsFileDescription', { defaultMessage: 'Administrative boundaries from Elastic Maps Service', diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx index 315759a2eba29..bfa46574f007a 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx @@ -13,13 +13,14 @@ import { EMSTMSSource, sourceTitle } from './ems_tms_source'; import { VectorTileLayer } from '../../layers/vector_tile_layer/vector_tile_layer'; // @ts-ignore import { TileServiceSelect } from './tile_service_select'; -import { getIsEmsEnabled } from '../../../kibana_services'; +import { getEMSSettings } from '../../../kibana_services'; import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const emsBaseMapLayerWizardConfig: LayerWizard = { categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: async () => { - return getIsEmsEnabled(); + const emsSettings = getEMSSettings(); + return emsSettings!.isEMSEnabled(); }, description: i18n.translate('xpack.maps.source.emsTileDescription', { defaultMessage: 'Tile map service from Elastic Maps Service', diff --git a/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx b/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx index dea161fafd609..ba897b7d9da0c 100644 --- a/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx +++ b/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx @@ -5,15 +5,14 @@ */ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { getIsEmsEnabled } from '../kibana_services'; +import { getEMSSettings } from '../kibana_services'; export function getEmsUnavailableMessage(): string { - const isEmsEnabled = getIsEmsEnabled(); + const isEmsEnabled = getEMSSettings().isEMSEnabled(); if (isEmsEnabled) { return i18n.translate('xpack.maps.source.ems.noAccessDescription', { defaultMessage: - 'Kibana is unable to access Elastic Maps Service. Contact your system administrator', + 'Kibana is unable to access Elastic Maps Service. Contact your system administrator.', }); } diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 08ee4b6628dd1..782c37a72d99b 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -9,6 +9,7 @@ import { CoreStart } from 'kibana/public'; import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; import { MapsConfigType } from '../config'; import { MapsPluginStartDependencies } from './plugin'; +import { EMSSettings } from '../common/ems_settings'; let kibanaVersion: string; export const setKibanaVersion = (version: string) => (kibanaVersion = version); @@ -62,14 +63,16 @@ let kibanaCommonConfig: MapsLegacyConfig; export const setKibanaCommonConfig = (config: MapsLegacyConfig) => (kibanaCommonConfig = config); export const getKibanaCommonConfig = () => kibanaCommonConfig; -export const getIsEmsEnabled = () => getKibanaCommonConfig().includeElasticMapsService; -export const getEmsFontLibraryUrl = () => getKibanaCommonConfig().emsFontLibraryUrl; +let emsSettings: EMSSettings; +export const setEMSSettings = (value: EMSSettings) => { + emsSettings = value; +}; +export const getEMSSettings = () => { + return emsSettings; +}; + export const getEmsTileLayerId = () => getKibanaCommonConfig().emsTileLayerId; -export const getEmsFileApiUrl = () => getKibanaCommonConfig().emsFileApiUrl; -export const getEmsTileApiUrl = () => getKibanaCommonConfig().emsTileApiUrl; -export const getEmsLandingPageUrl = () => getKibanaCommonConfig().emsLandingPageUrl; -export const getProxyElasticMapsServiceInMaps = () => - getKibanaCommonConfig().proxyElasticMapsServiceInMaps; + export const getRegionmapLayers = () => _.get(getKibanaCommonConfig(), 'regionmap.layers', []); export const getTilemap = () => _.get(getKibanaCommonConfig(), 'tilemap', []); diff --git a/x-pack/plugins/maps/public/licensed_features.ts b/x-pack/plugins/maps/public/licensed_features.ts index 67fa526da0cbd..13809f2b26a8c 100644 --- a/x-pack/plugins/maps/public/licensed_features.ts +++ b/x-pack/plugins/maps/public/licensed_features.ts @@ -27,9 +27,13 @@ export const LICENCED_FEATURES_DETAILS: Record licenseId; export const getIsGoldPlus = () => isGoldPlus; +export const getIsEnterprisePlus = () => isEnterprisePlus; + export function registerLicensedFeatures(licensingPlugin: LicensingPluginSetup) { for (const licensedFeature of Object.values(LICENSED_FEATURES)) { licensingPlugin.featureUsage.register( @@ -45,6 +49,10 @@ export function setLicensingPluginStart(licensingPlugin: LicensingPluginStart) { licensingPluginStart.license$.subscribe((license: ILicense) => { const gold = license.check(APP_ID, 'gold'); isGoldPlus = gold.state === 'valid'; + + const enterprise = license.check(APP_ID, 'enterprise'); + isEnterprisePlus = enterprise.state === 'valid'; + licenseId = license.uid; }); } diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js index c414c8a2d400e..d4f9885830b54 100644 --- a/x-pack/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -9,14 +9,22 @@ import { getEMSClient, getGlyphUrl } from './meta'; jest.mock('@elastic/ems-client'); +const EMS_FONTS_URL_MOCK = 'ems/fonts'; +const MOCK_EMS_SETTINGS = { + isEMSEnabled: () => true, + getEMSFileApiUrl: () => 'https://file-api', + getEMSTileApiUrl: () => 'https://tile-api', + getEMSLandingPageUrl: () => 'http://test.com', + getEMSFontLibraryUrl: () => EMS_FONTS_URL_MOCK, + isProxyElasticMapsServiceInMaps: () => false, +}; + describe('default use without proxy', () => { beforeEach(() => { - require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false; - require('./kibana_services').getIsEmsEnabled = () => true; require('./kibana_services').getEmsTileLayerId = () => '123'; - require('./kibana_services').getEmsFileApiUrl = () => 'https://file-api'; - require('./kibana_services').getEmsTileApiUrl = () => 'https://tile-api'; - require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com'; + require('./kibana_services').getEMSSettings = () => { + return MOCK_EMS_SETTINGS; + }; require('./licensed_features').getLicenseId = () => { return 'foobarlicenseid'; }; @@ -32,10 +40,7 @@ describe('default use without proxy', () => { describe('getGlyphUrl', () => { describe('EMS enabled', () => { - const EMS_FONTS_URL_MOCK = 'ems/fonts'; beforeAll(() => { - require('./kibana_services').getIsEmsEnabled = () => true; - require('./kibana_services').getEmsFontLibraryUrl = () => EMS_FONTS_URL_MOCK; require('./kibana_services').getHttp = () => ({ basePath: { prepend: (url) => url, // No need to actually prepend a dev basepath for test @@ -45,7 +50,12 @@ describe('getGlyphUrl', () => { describe('EMS proxy enabled', () => { beforeAll(() => { - require('./kibana_services').getProxyElasticMapsServiceInMaps = () => true; + require('./kibana_services').getEMSSettings = () => { + return { + ...MOCK_EMS_SETTINGS, + isProxyElasticMapsServiceInMaps: () => true, + }; + }; }); test('should return proxied EMS fonts URL', async () => { @@ -55,7 +65,12 @@ describe('getGlyphUrl', () => { describe('EMS proxy disabled', () => { beforeAll(() => { - require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false; + require('./kibana_services').getEMSSettings = () => { + return { + ...MOCK_EMS_SETTINGS, + isProxyElasticMapsServiceInMaps: () => false, + }; + }; }); test('should return EMS fonts URL', async () => { @@ -72,7 +87,12 @@ describe('getGlyphUrl', () => { }, }; require('./kibana_services').getHttp = () => mockHttp; - require('./kibana_services').getIsEmsEnabled = () => false; + require('./kibana_services').getEMSSettings = () => { + return { + ...MOCK_EMS_SETTINGS, + isEMSEnabled: () => false, + }; + }; }); test('should return kibana fonts URL', async () => { diff --git a/x-pack/plugins/maps/public/meta.ts b/x-pack/plugins/maps/public/meta.ts index 929050338de72..5632e226478a7 100644 --- a/x-pack/plugins/maps/public/meta.ts +++ b/x-pack/plugins/maps/public/meta.ts @@ -18,15 +18,10 @@ import { } from '../common/constants'; import { getHttp, - getIsEmsEnabled, getRegionmapLayers, getTilemap, - getEmsFileApiUrl, - getEmsTileApiUrl, - getEmsLandingPageUrl, - getEmsFontLibraryUrl, - getProxyElasticMapsServiceInMaps, getKibanaVersion, + getEMSSettings, } from './kibana_services'; import { getLicenseId } from './licensed_features'; import { LayerConfig } from '../../../../src/plugins/region_map/config'; @@ -40,7 +35,7 @@ export function getKibanaTileMap(): unknown { } export async function getEmsFileLayers(): Promise { - if (!getIsEmsEnabled()) { + if (!getEMSSettings().isEMSEnabled()) { return []; } @@ -48,7 +43,7 @@ export async function getEmsFileLayers(): Promise { } export async function getEmsTmsServices(): Promise { - if (!getIsEmsEnabled()) { + if (!getEMSSettings().isEMSEnabled()) { return []; } @@ -65,18 +60,18 @@ let emsClient: EMSClient | null = null; let latestLicenseId: string | undefined; export function getEMSClient(): EMSClient { if (!emsClient) { - const proxyElasticMapsServiceInMaps = getProxyElasticMapsServiceInMaps(); + const emsSettings = getEMSSettings(); const proxyPath = ''; - const tileApiUrl = proxyElasticMapsServiceInMaps + const tileApiUrl = emsSettings!.isProxyElasticMapsServiceInMaps() ? relativeToAbsolute( getHttp().basePath.prepend(`/${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}`) ) - : getEmsTileApiUrl(); - const fileApiUrl = proxyElasticMapsServiceInMaps + : emsSettings!.getEMSTileApiUrl(); + const fileApiUrl = emsSettings!.isProxyElasticMapsServiceInMaps() ? relativeToAbsolute( getHttp().basePath.prepend(`/${GIS_API_PATH}/${EMS_FILES_CATALOGUE_PATH}`) ) - : getEmsFileApiUrl(); + : emsSettings!.getEMSFileApiUrl(); emsClient = new EMSClient({ language: i18n.getLocale(), @@ -84,7 +79,7 @@ export function getEMSClient(): EMSClient { appName: EMS_APP_NAME, tileApiUrl, fileApiUrl, - landingPageUrl: getEmsLandingPageUrl(), + landingPageUrl: emsSettings!.getEMSLandingPageUrl(), fetchFunction(url: string) { return fetch(url); }, @@ -100,16 +95,18 @@ export function getEMSClient(): EMSClient { } export function getGlyphUrl(): string { - if (!getIsEmsEnabled()) { + const emsSettings = getEMSSettings(); + if (!emsSettings!.isEMSEnabled()) { return getHttp().basePath.prepend(`/${FONTS_API_PATH}/{fontstack}/{range}`); } - return getProxyElasticMapsServiceInMaps() + + return emsSettings!.isProxyElasticMapsServiceInMaps() ? relativeToAbsolute( getHttp().basePath.prepend( `/${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}` ) ) + `/{fontstack}/{range}` - : getEmsFontLibraryUrl(); + : emsSettings!.getEMSFontLibraryUrl(); } export function isRetina(): boolean { diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 75a3f8ef5ede8..b79a2b06b9b37 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -19,6 +19,7 @@ import { // @ts-ignore import { MapView } from './inspector/views/map_view'; import { + setEMSSettings, setKibanaCommonConfig, setKibanaVersion, setMapAppConfig, @@ -55,7 +56,12 @@ import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public'; import { StartContract as FileUploadStartContract } from '../../file_upload/public'; import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public'; -import { registerLicensedFeatures, setLicensingPluginStart } from './licensed_features'; +import { + getIsEnterprisePlus, + registerLicensedFeatures, + setLicensingPluginStart, +} from './licensed_features'; +import { EMSSettings } from '../common/ems_settings'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -111,6 +117,9 @@ export class MapsPlugin setMapAppConfig(config); setKibanaVersion(this._initializerContext.env.packageInfo.version); + const emsSettings = new EMSSettings(plugins.mapsLegacy.config, getIsEnterprisePlus); + setEMSSettings(emsSettings); + // register url generators const getStartServices = async () => { const [coreStart] = await core.getStartServices(); diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js index 4de29e6f028e1..66adb1da6900e 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js @@ -15,7 +15,11 @@ const layerListNotProvided = undefined; describe('Saved object has layer list', () => { beforeEach(() => { - require('../../kibana_services').getIsEmsEnabled = () => true; + require('../../kibana_services').getEMSSettings = () => { + return { + isEMSEnabled: () => true, + }; + }; }); it('Should get initial layers from saved object', () => { @@ -65,7 +69,11 @@ describe('EMS is enabled', () => { require('../../meta').getKibanaTileMap = () => { return null; }; - require('../../kibana_services').getIsEmsEnabled = () => true; + require('../../kibana_services').getEMSSettings = () => { + return { + isEMSEnabled: () => true, + }; + }; require('../../kibana_services').getEmsTileLayerId = () => ({ bright: 'road_map', desaturated: 'road_map_desaturated', @@ -101,7 +109,11 @@ describe('EMS is not enabled', () => { require('../../meta').getKibanaTileMap = () => { return null; }; - require('../../kibana_services').getIsEmsEnabled = () => false; + require('../../kibana_services').getEMSSettings = () => { + return { + isEMSEnabled: () => false, + }; + }; }); it('Should return empty layer list since there are no configured tile layers', () => { diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts index e828dc88409cb..c887320873995 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts +++ b/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts @@ -23,7 +23,7 @@ import { TileLayer } from '../../classes/layers/tile_layer/tile_layer'; import { EMSTMSSource } from '../../classes/sources/ems_tms_source'; // @ts-expect-error import { VectorTileLayer } from '../../classes/layers/vector_tile_layer/vector_tile_layer'; -import { getIsEmsEnabled, getToasts } from '../../kibana_services'; +import { getEMSSettings, getToasts } from '../../kibana_services'; import { INITIAL_LAYERS_KEY } from '../../../common/constants'; import { getKibanaTileMap } from '../../meta'; @@ -39,7 +39,7 @@ export function getInitialLayers(layerListJSON?: string, initialLayers: LayerDes return [layerDescriptor, ...initialLayers]; } - const isEmsEnabled = getIsEmsEnabled(); + const isEmsEnabled = getEMSSettings()!.isEMSEnabled(); if (isEmsEnabled) { const layerDescriptor = VectorTileLayer.createDescriptor({ sourceDescriptor: EMSTMSSource.createDescriptor({ isAutoSelect: true }), diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 00950e96047a0..65d79272494f0 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -28,7 +28,7 @@ import { ILicense } from '../../licensing/common/types'; import { LicensingPluginSetup } from '../../licensing/server'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { MapsLegacyPluginSetup } from '../../../../src/plugins/maps_legacy/server'; -import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config'; +import { EMSSettings } from '../common/ems_settings'; interface SetupDeps { features: FeaturesPluginSetupContract; @@ -52,7 +52,7 @@ export class MapsPlugin implements Plugin { _initHomeData( home: HomeServerPluginSetup, prependBasePath: (path: string) => string, - mapsLegacyConfig: MapsLegacyConfig + emsSettings: EMSSettings ) { const sampleDataLinkLabel = i18n.translate('xpack.maps.sampleDataLinkLabel', { defaultMessage: 'Map', @@ -125,7 +125,7 @@ export class MapsPlugin implements Plugin { home.tutorials.registerTutorial( emsBoundariesSpecProvider({ prependBasePath, - emsLandingPageUrl: mapsLegacyConfig.emsLandingPageUrl, + emsLandingPageUrl: emsSettings.getEMSLandingPageUrl(), }) ); } @@ -148,21 +148,27 @@ export class MapsPlugin implements Plugin { } let routesInitialized = false; + let isEnterprisePlus = false; + const emsSettings = new EMSSettings(mapsLegacyConfig, () => isEnterprisePlus); licensing.license$.subscribe((license: ILicense) => { - const { state } = license.check('maps', 'basic'); - if (state === 'valid' && !routesInitialized) { + const basic = license.check(APP_ID, 'basic'); + + const enterprise = license.check(APP_ID, 'enterprise'); + isEnterprisePlus = enterprise.state === 'valid'; + + if (basic.state === 'valid' && !routesInitialized) { routesInitialized = true; initRoutes( core.http.createRouter(), license.uid, - mapsLegacyConfig, + emsSettings, this.kibanaVersion, this._logger ); } }); - this._initHomeData(home, core.http.basePath.prepend, mapsLegacyConfig); + this._initHomeData(home, core.http.basePath.prepend, emsSettings); features.registerKibanaFeature({ id: APP_ID, diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 5feacfee4d4d2..49d646f9a4e6d 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -33,38 +33,45 @@ import fs from 'fs'; import path from 'path'; import { initMVTRoutes } from './mvt/mvt_routes'; -export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { +const EMPTY_EMS_CLIENT = { + async getFileLayers() { + return []; + }, + async getTMSServices() { + return []; + }, + async getMainManifest() { + return null; + }, + async getDefaultFileManifest() { + return null; + }, + async getDefaultTMSManifest() { + return null; + }, + addQueryParams() {}, +}; + +export function initRoutes(router, licenseUid, emsSettings, kbnVersion, logger) { let emsClient; - if (mapConfig.includeElasticMapsService) { + + if (emsSettings.isIncludeElasticMapsService()) { emsClient = new EMSClient({ language: i18n.getLocale(), appVersion: kbnVersion, appName: EMS_APP_NAME, - fileApiUrl: mapConfig.emsFileApiUrl, - tileApiUrl: mapConfig.emsTileApiUrl, - landingPageUrl: mapConfig.emsLandingPageUrl, + fileApiUrl: emsSettings.getEMSFileApiUrl(), + tileApiUrl: emsSettings.getEMSTileApiUrl(), + landingPageUrl: emsSettings.getEMSLandingPageUrl(), fetchFunction: fetch, }); emsClient.addQueryParams({ license: licenseUid }); } else { - emsClient = { - async getFileLayers() { - return []; - }, - async getTMSServices() { - return []; - }, - async getMainManifest() { - return null; - }, - async getDefaultFileManifest() { - return null; - }, - async getDefaultTMSManifest() { - return null; - }, - addQueryParams() {}, - }; + emsClient = EMPTY_EMS_CLIENT; + } + + function getEMSClient() { + return emsSettings.isEMSEnabled() ? emsClient : EMPTY_EMS_CLIENT; } router.get( @@ -90,7 +97,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return null; } - const fileLayers = await emsClient.getFileLayers(); + const fileLayers = await getEMSClient().getFileLayers(); const layer = fileLayers.find((layer) => layer.getId() === request.query.id); if (!layer) { return null; @@ -127,7 +134,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return null; } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; @@ -153,7 +160,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const main = await emsClient.getMainManifest(); + const main = await getEMSClient().getMainManifest(); const proxiedManifest = { services: [], }; @@ -189,8 +196,8 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const file = await emsClient.getDefaultFileManifest(); //need raw manifest - const fileLayers = await emsClient.getFileLayers(); + const file = await getEMSClient().getDefaultFileManifest(); //need raw manifest + const fileLayers = await getEMSClient().getFileLayers(); const layers = file.layers.map((layerJson) => { const newLayerJson = { ...layerJson }; @@ -231,7 +238,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const tilesManifest = await emsClient.getDefaultTMSManifest(); + const tilesManifest = await getEMSClient().getDefaultTMSManifest(); const newServices = tilesManifest.services.map((service) => { const newService = { ...service, @@ -284,7 +291,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return null; } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; @@ -319,7 +326,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; @@ -368,7 +375,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; @@ -409,7 +416,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return response.badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.query.id); if (!tmsService) { return null; @@ -439,7 +446,8 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { if (!checkEMSProxyEnabled()) { return response.badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const url = mapConfig.emsFontLibraryUrl + const url = emsSettings + .getEMSFontLibraryUrl() .replace('{fontstack}', request.params.fontstack) .replace('{range}', request.params.range); @@ -469,7 +477,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { return response.badRequest('map.proxyElasticMapsServiceInMaps disabled'); } - const tmsServices = await emsClient.getTMSServices(); + const tmsServices = await getEMSClient().getTMSServices(); const tmsService = tmsServices.find((layer) => layer.getId() === request.params.id); if (!tmsService) { return null; @@ -573,7 +581,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { ); function checkEMSProxyEnabled() { - const proxyEMSInMaps = mapConfig.proxyElasticMapsServiceInMaps; + const proxyEMSInMaps = emsSettings.isProxyElasticMapsServiceInMaps(); if (!proxyEMSInMaps) { logger.warn( `Cannot load content from EMS when map.proxyElasticMapsServiceInMaps is turned off` From 1b86d79319efa8c5b153a90099d20babde208b92 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 10 Nov 2020 09:11:21 -0600 Subject: [PATCH 52/87] [DOCS] Consolidates drilldown pages (#82081) * [DOCS] Consolidated drilldowns * Review comments pt 1 * Update docs/user/dashboard/drilldowns.asciidoc Co-authored-by: Anton Dosov * Fixes supported drilldowns link * Update src/core/public/doc_links/doc_links_service.ts Co-authored-by: Anton Dosov * Fixes rogue disable section and fixes intro formatting * Fixes URL drilldown link Co-authored-by: Anton Dosov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../dashboard/dashboard-drilldown.asciidoc | 96 ------- docs/user/dashboard/drilldowns.asciidoc | 269 ++++++++++++++++-- docs/user/dashboard/url-drilldown.asciidoc | 115 +------- .../public/doc_links/doc_links_service.ts | 6 +- 4 files changed, 249 insertions(+), 237 deletions(-) delete mode 100644 docs/user/dashboard/dashboard-drilldown.asciidoc diff --git a/docs/user/dashboard/dashboard-drilldown.asciidoc b/docs/user/dashboard/dashboard-drilldown.asciidoc deleted file mode 100644 index bdff7355d7467..0000000000000 --- a/docs/user/dashboard/dashboard-drilldown.asciidoc +++ /dev/null @@ -1,96 +0,0 @@ -[[dashboard-drilldown]] -=== Dashboard drilldown - -The dashboard drilldown allows you to navigate from one dashboard to another dashboard. -For example, you might have a dashboard that shows the overall status of multiple data centers. -You can create a drilldown that navigates from this dashboard to a dashboard -that shows a single data center or server. - -This example shows a dashboard panel that contains a pie chart with a configured dashboard drilldown: - -[role="screenshot"] -image::images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] - -[float] -[[dashboard-drilldown-supported-panels]] -==== Supported panels - -The following panels support dashboard drilldowns: - -* Lens -* Area -* Data table -* Heat map -* Horizontal bar -* Line -* Maps -* Pie -* TSVB -* Tag cloud -* Timelion -* Vega -* Vertical bar - -[float] -[[drilldowns-example]] -==== Try it: Create a dashboard drilldown - -Create the *Host Overview* drilldown shown above. - -*Set up the dashboards* - -. Add the sample web logs data set. - -. Create a new dashboard, called `Host Overview`, and include these visualizations -from the sample data set: -+ -[%hardbreaks] -*[Logs] Heatmap* -*[Logs] Visitors by OS* -*[Logs] Host, Visits, and Bytes Table* -*[Logs] Total Requests and Bytes* -+ -TIP: If you don’t see data for a panel, try changing the time range. - -. Open the *[Logs] Web traffic* dashboard. - -. Set a search and filter. -+ -[%hardbreaks] -Search: `extension.keyword: ("gz" or "css" or "deb")` -Filter: `geo.src: CN` - - -*Create the drilldown* - - -. In the dashboard menu bar, click *Edit*. - -. In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. - -. Pick *Go to dashboard* action. - -. Give the drilldown a name. - -. Select *Host Overview* as the destination dashboard. - -. Keep both filters enabled so that the drilldown carries over the global filters and date range. -+ -Your input should look similar to this: -+ -[role="screenshot"] -image::images/drilldown_create.png[Create drilldown with entries for drilldown name and destination] - -. Click *Create drilldown.* - -. Save the dashboard. -+ -If you don’t save the drilldown, and then navigate away, the drilldown is lost. - -. In *[Logs] Visitors by OS*, click the `win 8` slice of the pie, and then select the name of your drilldown. -+ -[role="screenshot"] -image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to another dashboard] -+ -You are navigated to your destination dashboard. Verify that the search query, filters, -and time range are carried over. diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index e3d0e16630c5c..ca788020d9286 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -1,52 +1,259 @@ [role="xpack"] [[drilldowns]] -== Use drilldowns for dashboard actions +== Create custom dashboard actions -Drilldowns, also known as custom actions, allow you to configure a -workflow for analyzing and troubleshooting your data. -For example, using a drilldown, you can navigate from one dashboard to another, -taking the current time range, filters, and other parameters with you, -so the context remains the same. You can continue your analysis from a new perspective. +Custom dashboard actions, also known as drilldowns, allow you to create +workflows for analyzing and troubleshooting your data. Drilldowns apply only to the panel that you created the drilldown from, and are not shared across all of the panels. Each panel can have multiple drilldowns. -[role="screenshot"] -image::images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] +Third-party developers can create drilldowns. To learn how to code drilldowns, refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin]. + +[float] +[[supported-drilldowns]] +=== Supported drilldowns + +{kib} supports two types of drilldowns. + +[NOTE] +============================================== +Some drilldowns are paid subscription features, while others are free. +For a comparison of the Elastic subscription levels, +refer https://www.elastic.co/subscriptions[the subscription page]. +============================================== + +[float] +[[dashboard-drilldown]] +==== Dashboard drilldowns + +Dashboard drilldowns enable you to open a dashboard from another dashboard, +taking the time range, filters, and other parameters with you, +so the context remains the same. Dashboard drilldowns help you to continue your analysis from a new perspective. + +For example, if you have a dashboard that shows the overall status of multiple data center, +you can create a drilldown that navigates from the overall status dashboard to a dashboard +that shows a single data center or server. + +[float] +[[url-drilldown]] +==== URL drilldowns + +beta[] URL drilldowns enable you to navigate from a dashboard to internal or external URLs. +Destination URLs can be dynamic, depending on the dashboard context or user interaction with a panel. +For example, if you have a dashboard that shows data from a Github repository, you can create a URL drilldown +that opens Github from the dashboard. + +Some panels support multiple interactions, also known as triggers. +The <> you use to create a <> depends on the trigger you choose. URL drilldowns support these types of triggers: + +* *Single click* — A single data point in the visualization. + +* *Range selection* — A range of values in a visualization. + +For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`. + +To disable URL drilldowns on your {kib} instance, disable the plugin: -Drilldowns are specific to the dashboard panel for which you create them—they are not shared across panels. A panel can have multiple drilldowns. +["source","yml"] +----------- +url_drilldown.enabled: false +----------- [float] -[[actions]] -=== Drilldown actions +[[dashboard-drilldown-supported-panels]] +=== Supported panels -Drilldowns are user-configurable {kib} actions that are stored with the dashboard metadata. -Kibana provides the following types of actions: +The following panels support dashboard and URL drilldowns. -[cols="2"] +[options="header"] |=== -a| <> +| Panel | Dashboard drilldown | URL drilldown -| Navigate to a dashboard. +| Lens +^| X +^| X -a| <> +| Area +^| X +^| X -| Navigate to external or internal URL. +| Controls +^| +^| + +| Data Table +^| X +^| X + +| Gauge +^| +^| + +| Goal +^| +^| + +| Heat map +^| X +^| X + +| Horizontal Bar +^| X +^| X + +| Line +^| X +^| X + +| Maps +^| X +^| + +| Markdown +^| +^| + +| Metric +^| +^| + +| Pie +^| X +^| X + +| TSVB +^| X +^| + +| Tag Cloud +^| X +^| X + +| Timelion +^| X +^| + +| Vega +^| X +^| + +| Vertical Bar +^| X +^| X |=== -[NOTE] -============================================== -Some action types are paid commercial features, while others are free. -For a comparison of the Elastic subscription levels, -see https://www.elastic.co/subscriptions[the subscription page]. -============================================== +[float] +[[drilldowns-example]] +=== Try it: Create a dashboard drilldown + +To create dashboard drilldowns, you create or locate the dashboards you want to connect, then configure the drilldown that allows you to easily open one dashboard from the other dashboard. + +image:images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] [float] -[[code-drilldowns]] -=== Code drilldowns -Third-party developers can create drilldowns. -Refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin] -to learn how to code drilldowns. +==== Create the dashboard -include::dashboard-drilldown.asciidoc[] -include::url-drilldown.asciidoc[] +. Add the *Sample web logs* data. + +. Create a new dashboard, then add the following panels: + +* *[Logs] Heatmap* +* *[Logs] Host, Visits, and Bytes Table* +* *[Logs] Total Requests and Bytes* +* *[Logs] Visitors by OS* ++ +If you don’t see data for a panel, try changing the <>. + +. Save the dashboard. In the *Title* field, enter `Host Overview`. + +. Open the *[Logs] Web traffic* dashboard. + +. Set a search and filter. ++ +[%hardbreaks] +Search: `extension.keyword: ("gz" or "css" or "deb")` +Filter: `geo.src: CN` + +[float] +==== Create the drilldown + +. In the toolbar, click *Edit*. + +. Open the *[Logs] Visitors by OS* panel menu, then select *Create drilldown*. + +. Give the drilldown a name, then select *Go to dashboard*. + +. From the *Choose a destination dashboard* dropdown, select *Host Overview*. + +. To carry over the filter, query, and date range, make sure that *Use filters and query from origin dashboard* and *Use date range from origin dashboard* are selected. ++ +[role="screenshot"] +image::images/drilldown_create.png[Create drilldown with entries for drilldown name and destination] + +. Click *Create drilldown*. ++ +The drilldown is stored as dashboard metadata. + +. Save the dashboard. ++ +If you fail to save the dashboard, the drilldown is lost when you navigate away from the dashboard. + +. In the *[Logs] Visitors by OS* panel, click *win 8*, then select the drilldown. ++ +[role="screenshot"] +image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to another dashboard] +. On the *Host Overview* dashboard, verify that the search query, filters, +and date range are carried over. + +[float] +[[create-a-url-drilldown]] +=== Try it: Create a URL drilldown + +beta[] To create URL drilldowns, you add <> to a URL template, which configures the bahavior of the drilldown. + +image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] + +. Add the *Sample web logs* data. + +. Open the *[Logs] Web traffic* dashboard. This isn’t data from Github, but works for demonstration purposes. + +. In the toolbar, click *Edit*. + +. Open the *[Logs] Visitors by OS* panel menu, then select *Create drilldown*. + +.. In the *Name* field, enter `Show on Github`. + +.. Select *Go to URL*. + +.. Enter the URL template: ++ +[source, bash] +---- +https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} +---- ++ +The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice. In *URL preview*, `{{event.value}}` is substituted with a <> value. ++ +[role="screenshot"] +image:images/url_drilldown_url_template.png[URL template input] + +.. Click *Create drilldown*. ++ +The drilldown is stored as dashboard metadata. + +. Save the dashboard. ++ +If you fail to save the dashboard, the drilldown is lost when you navigate away from the dashboard. + +. On the *[Logs] Visitors by OS* panel, click any chart slice, then select *Show on Github*. ++ +[role="screenshot"] +image:images/url_drilldown_popup.png[URL drilldown popup] + +. On the page that lists the issues in the {kib} repository, verify the slice value appears in Github. ++ +[role="screenshot"] +image:images/url_drilldown_github.png[Github] + +include::url-drilldown.asciidoc[] diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc index 212e29898bd40..872d83bfd9009 100644 --- a/docs/user/dashboard/url-drilldown.asciidoc +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -1,96 +1,9 @@ -[[url-drilldown]] -=== URL drilldown +[[url_templating]] +=== URL templating beta[] -The URL drilldown allows you to navigate from a dashboard to an internal or external URL. -The destination URL can be dynamic, depending on the dashboard context or user’s interaction with a visualization. - -For example, you might have a dashboard that shows data from a Github repository. -You can create a drilldown that navigates from this dashboard to Github. - -[role="screenshot"] -image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] - -NOTE: URL drilldown is available with the https://www.elastic.co/subscriptions[Gold subscription] and higher. - -[float] -[[url-drilldown-supported-panels]] -==== Supported panels - -The following panels support URL drilldowns: - -* Lens -* Area -* Data table -* Heat map -* Horizontal bar -* Line -* Pie -* Tag cloud -* Vertical bar - -[float] -[[try-it]] -==== Try it: Create a URL drilldown - -This example shows how to create the "Show on Github" drilldown shown above. - -. Add the sample web logs data set. -. Open the *[Logs] Web traffic* dashboard. This isn’t data from Github, but it should work for demonstration purposes. -. In the dashboard menu bar, click *Edit*. -. In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. -. Give the drilldown a name: *Show on Github*. -. Select a drilldown action: *Go to URL*. -+ -[role="screenshot"] -image:images/url_drilldown_pick_an_action.png[Action picker] -. Enter a URL template: -+ -[source, bash] ----- -https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} ----- -+ -This example URL navigates to {kib} issues on Github. `{{event.value}}` will be substituted with a value associated with a clicked pie slice. In _preview_ `{{event.value}}` is substituted with a <> value. -[role="screenshot"] -image:images/url_drilldown_url_template.png[URL template input] -. Click *Create drilldown*. -. Save the dashboard. -+ -If you don’t save the drilldown, and then navigate away, the drilldown is lost. - -. In *[Logs] Visitors by OS*, click any slice of the pie, and then select the drilldown *Show on Github*. -+ -[role="screenshot"] -image:images/url_drilldown_popup.png[URL drilldown popup] -+ -You are navigated to the issue list in the {kib} repository. Verify that value from a pie slice you’ve clicked on is carried over to Github. -+ -[role="screenshot"] -image:images/url_drilldown_github.png[Github] - -[float] -[[trigger-picker]] -==== Picking a trigger for a URL drilldown - -Some panels support multiple user interactions (called triggers) for which you can configure a URL drilldown. The list of supported variables in the URL template depends on the trigger you selected. -In the preceding example, you configured a URL drilldown on a pie chart. The only trigger that pie chart supports is clicking on a pie slice, so you didn’t have to pick a trigger. - -However, the sample *[Logs] Unique Visitors vs. Average Bytes* chart supports both clicking on a data point and selecting a range. When you create a URL drilldown for this chart, you have the following choices: - -[role="screenshot"] -image:images/url_drilldown_trigger_picker.png[Trigger picker: Single click and Range selection] - -Variables in the URL template differ per trigger. -For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`. -You can create multiple URL drilldowns per panel and attach them to different triggers. - -[float] -[[templating]] -==== URL templating language - -The URL template input uses Handlebars — a simple templating language. Handlebars templates look like regular text with embedded Handlebars expressions. +The URL template input uses https://handlebarsjs.com/guide/expressions.html#expressions[Handlebars] — a simple templating language. Handlebars templates look like regular text with embedded Handlebars expressions. [source, bash] ---- @@ -99,14 +12,13 @@ https://github.com/elastic/kibana/issues?q={{event.value}} A Handlebars expression is a `{{`, some contents, followed by a `}}`. When the drilldown is executed, these expressions are replaced by values from the dashboard and interaction context. -Refer to Handlebars https://ela.st/handlebars-docs#expressions[documentation] to learn about advanced use cases. - [[helpers]] -In addition to https://ela.st/handlebars-helpers[built-in] Handlebars helpers, you can use the following custom helpers: +In addition to https://handlebarsjs.com/guide/builtin-helpers.html[built-in] Handlebars helpers, you can use custom helpers. +Refer to Handlebars https://ela.st/handlebars-docs#expressions[documentation] to learn about advanced use cases. |=== -|Helper |Use case +|Custom helper |Use case |json a|Serialize variables in JSON format. @@ -133,7 +45,7 @@ a|Format dates. Supports relative dates expressions (for example, "now-15d"). R Example: -`{{ date event.from “YYYY MM DD”}}` + +`{{date event.from “YYYY MM DD”}}` + `{{date “now-15”}}` |formatNumber @@ -240,7 +152,7 @@ For example, `{{context.panel.filters}}` are previewed with the current filters *Event* variables are extracted during drilldown execution from a user interaction with a panel (for example, from a pie slice that the user clicked on). Because there is no user interaction with a panel in preview, there is no interaction context to use in a preview. -To work around this, {kib} provides a sample interaction that relies on a picked <>. +To work around this, {kib} provides a sample interaction that relies on a trigger. So in a preview, you might notice that `{{event.value}}` is replaced with `{{event.value}}` instead of with a sample from your data. Such previews can help you make sure that the structure of your URL template is valid. However, to ensure that the configured URL drilldown works as expected with your data, you have to save the dashboard and test in the panel. @@ -340,14 +252,3 @@ Tip: Consider using <> helper for date formatting. | Aggregation field behind the selected range, if available. |=== - -[float] -[[disable]] -==== Disable URL drilldown - -You can disable URL drilldown feature on your {kib} instance by disabling the plugin: - -["source","yml"] ------------ -url_drilldown.enabled: false ------------ diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 0815df4b9b0c7..6af14734444d1 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -39,9 +39,9 @@ export class DocLinksService { dashboard: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, - drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#trigger-picker`, - urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#templating`, - urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#variables`, + drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldown`, + urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html`, + urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#variables`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, From 2332c8b6d8eaae7c2bc96dfb1b2028286604b051 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 10 Nov 2020 08:25:32 -0700 Subject: [PATCH 53/87] [Maps] show icon when layer is filtered by time and allow layers to ignore global time range (#83006) * [Maps] show icon when layer is filtered by time and allow layers to ignore global time range * show icon if layer is narrowed by time fitler * tslint * apply global time to source check box * apply global time to join check box * tslint and jest expect updates * one more tslint fix * tslint, fix apm jest test, update time filter icon when disabling applyGlobalTime Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__tests__/__mocks__/regions_layer.mock.ts | 6 +- .../VisitorBreakdownMap/useLayerList.ts | 4 +- .../data_request_descriptor_types.ts | 10 +- .../source_descriptor_types.ts | 3 +- .../create_choropleth_layer_descriptor.ts | 2 + .../create_region_map_layer_descriptor.ts | 2 + .../maps/public/classes/layers/layer.test.ts | 2 + .../maps/public/classes/layers/layer.tsx | 83 +----- .../create_layer_descriptor.test.ts | 4 + .../observability/create_layer_descriptor.ts | 2 + .../security/create_layer_descriptors.test.ts | 6 + .../layers/vector_layer/vector_layer.tsx | 19 ++ .../es_agg_source/es_agg_source.test.ts | 12 +- .../es_geo_grid_source.test.ts | 1 + .../es_search_source/es_search_source.test.ts | 1 + .../classes/sources/es_source/es_source.ts | 22 +- .../sources/es_term_source/es_term_source.ts | 18 +- .../kibana_regionmap_source.ts | 4 - .../mvt_single_layer_vector_source.tsx | 4 - .../maps/public/classes/sources/source.ts | 7 +- .../sources/vector_source/vector_source.tsx | 2 +- .../public/classes/util/can_skip_fetch.ts | 7 +- .../components/global_time_checkbox.tsx | 32 ++ .../filter_editor/filter_editor.js | 29 +- .../layer_panel/join_editor/join_editor.tsx | 1 + .../layer_panel/join_editor/resources/join.js | 71 +++-- .../toc_entry_actions_popover.test.tsx.snap | 281 +++++++----------- .../toc_entry_actions_popover/index.ts | 10 +- .../toc_entry_actions_popover.test.tsx | 15 - .../toc_entry_actions_popover.tsx | 65 +--- .../toc_entry/toc_entry_button/index.ts | 20 ++ .../toc_entry_button/toc_entry_button.tsx | 184 ++++++++++++ 32 files changed, 553 insertions(+), 376 deletions(-) create mode 100644 x-pack/plugins/maps/public/components/global_time_checkbox.tsx create mode 100644 x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts create mode 100644 x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts index e564332583375..6d259a5a2e48c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__tests__/__mocks__/regions_layer.mock.ts @@ -21,6 +21,8 @@ export const mockLayerList = [ { leftField: 'iso2', right: { + applyGlobalQuery: true, + applyGlobalTime: true, type: 'ES_TERM_SOURCE', id: '3657625d-17b0-41ef-99ba-3a2b2938655c', indexPatternTitle: 'apm-*', @@ -38,7 +40,6 @@ export const mockLayerList = [ }, ], indexPatternId: 'apm_static_index_pattern_id', - applyGlobalQuery: true, }, }, ], @@ -46,7 +47,6 @@ export const mockLayerList = [ type: 'EMS_FILE', id: 'world_countries', tooltipProperties: ['name'], - applyGlobalQuery: true, }, style: { type: 'VECTOR', @@ -96,6 +96,8 @@ export const mockLayerList = [ { leftField: 'region_iso_code', right: { + applyGlobalQuery: true, + applyGlobalTime: true, type: 'ES_TERM_SOURCE', id: 'e62a1b9c-d7ff-4fd4-a0f6-0fdc44bb9e41', indexPatternTitle: 'apm-*', diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index bc45d58329f49..a1cdf7bb646e5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -43,6 +43,7 @@ const ES_TERM_SOURCE_COUNTRY: ESTermSourceDescriptor = { ], indexPatternId: APM_STATIC_INDEX_PATTERN_ID, applyGlobalQuery: true, + applyGlobalTime: true, }; const ES_TERM_SOURCE_REGION: ESTermSourceDescriptor = { @@ -56,6 +57,8 @@ const ES_TERM_SOURCE_REGION: ESTermSourceDescriptor = { language: 'kuery', }, indexPatternId: APM_STATIC_INDEX_PATTERN_ID, + applyGlobalQuery: true, + applyGlobalTime: true, }; const getWhereQuery = (serviceName: string) => { @@ -158,7 +161,6 @@ export function useLayerList() { type: 'EMS_FILE', id: 'world_countries', tooltipProperties: [COUNTRY_NAME], - applyGlobalQuery: true, }, style: getLayerStyle(TRANSACTION_DURATION_COUNTRY), id: 'e8d1d974-eed8-462f-be2c-f0004b7619b2', diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index fc691f339f34a..68fc784182a77 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -38,17 +38,17 @@ export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncM export type VectorSourceRequestMeta = MapFilters & { applyGlobalQuery: boolean; + applyGlobalTime: boolean; fieldNames: string[]; geogridPrecision?: number; sourceQuery?: MapQuery; sourceMeta: VectorSourceSyncMeta; }; -export type VectorJoinSourceRequestMeta = MapFilters & { - applyGlobalQuery: boolean; - fieldNames: string[]; - sourceQuery?: Query; -}; +export type VectorJoinSourceRequestMeta = Omit< + VectorSourceRequestMeta, + 'geogridPrecision' | 'sourceMeta' +> & { sourceQuery?: Query }; export type VectorStyleRequestMeta = MapFilters & { dynamicStyleFields: string[]; diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 3dc90a12513fd..a6afbe4d55f9b 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -18,7 +18,6 @@ export type AttributionDescriptor = { export type AbstractSourceDescriptor = { id?: string; type: string; - applyGlobalQuery?: boolean; }; export type EMSTMSSourceDescriptor = AbstractSourceDescriptor & { @@ -37,6 +36,8 @@ export type AbstractESSourceDescriptor = AbstractSourceDescriptor & { id: string; indexPatternId: string; geoField?: string; + applyGlobalQuery: boolean; + applyGlobalTime: boolean; }; export type AggDescriptor = { 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 61fb6ef54c207..43d1d39c170c0 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 @@ -61,6 +61,8 @@ function createChoroplethLayerDescriptor({ indexPatternTitle: rightIndexPatternTitle, term: rightTermField, metrics: [metricsDescriptor], + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], 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 8bf078806cfbc..8830831b8b656 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 @@ -87,6 +87,8 @@ export function createRegionMapLayerDescriptor({ indexPatternTitle: indexPatternTitle ? indexPatternTitle : indexPatternId, term: termsFieldName, metrics: [metricsDescriptor], + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts index 76df7c2af840a..e669ddf13e5ac 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -74,6 +74,8 @@ describe('cloneDescriptor', () => { metrics: [{ type: AGG_TYPE.COUNT }], term: 'myTermField', type: 'joinSource', + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index f75dae84b1723..7c76df7f6e877 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -8,9 +8,8 @@ import { Query } from 'src/plugins/data/public'; import _ from 'lodash'; import React, { ReactElement } from 'react'; -import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import uuid from 'uuid/v4'; -import { i18n } from '@kbn/i18n'; import { FeatureCollection } from 'geojson'; import { DataRequest } from '../util/data_request'; import { @@ -49,8 +48,6 @@ export interface ILayer { supportsFitToBounds(): Promise; getAttributions(): Promise; getLabel(): string; - getCustomIconAndTooltipContent(): CustomIconAndTooltipContent; - getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent; renderLegendDetails(): ReactElement | null; showAtZoomLevel(zoom: number): boolean; getMinZoom(): number; @@ -64,6 +61,7 @@ export interface ILayer { getImmutableSourceProperties(): Promise; renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; isLayerLoading(): boolean; + isFilteredByGlobalTime(): Promise; hasErrors(): boolean; getErrors(): string; getMbLayerIds(): string[]; @@ -93,16 +91,9 @@ export interface ILayer { getJoinsDisabledReason(): string | null; isFittable(): Promise; getLicensedFeatures(): Promise; + getCustomIconAndTooltipContent(): CustomIconAndTooltipContent; } -export type Footnote = { - icon: ReactElement; - message?: string | null; -}; -export type IconAndTooltipContent = { - icon?: ReactElement | null; - tooltipContent?: string | null; - footnotes: Footnote[]; -}; + export type CustomIconAndTooltipContent = { icon: ReactElement | null; tooltipContent?: string | null; @@ -237,6 +228,10 @@ export class AbstractLayer implements ILayer { return (await this.supportsFitToBounds()) && this.isVisible(); } + async isFilteredByGlobalTime(): Promise { + return false; + } + async getDisplayName(source?: ISource): Promise { if (this._descriptor.label) { return this._descriptor.label; @@ -277,68 +272,6 @@ export class AbstractLayer implements ILayer { }; } - getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent { - let icon; - let tooltipContent = null; - const footnotes = []; - if (this.hasErrors()) { - icon = ( - - ); - tooltipContent = this.getErrors(); - } else if (this.isLayerLoading()) { - icon = ; - } else if (!this.isVisible()) { - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { - defaultMessage: `Layer is hidden.`, - }); - } else if (!this.showAtZoomLevel(zoomLevel)) { - const minZoom = this.getMinZoom(); - const maxZoom = this.getMaxZoom(); - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { - defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, - values: { minZoom, maxZoom }, - }); - } else { - const customIconAndTooltipContent = this.getCustomIconAndTooltipContent(); - if (customIconAndTooltipContent) { - icon = customIconAndTooltipContent.icon; - if (!customIconAndTooltipContent.areResultsTrimmed) { - tooltipContent = customIconAndTooltipContent.tooltipContent; - } else { - footnotes.push({ - icon: , - message: customIconAndTooltipContent.tooltipContent, - }); - } - } - - if (isUsingSearch && this.getQueryableIndexPatternIds().length) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { - defaultMessage: 'Results narrowed by search bar', - }), - }); - } - } - - return { - icon, - tooltipContent, - footnotes, - }; - } - async hasLegendDetails(): Promise { return false; } diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts index 66eba3a539801..e2678ee218b4b 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts @@ -39,6 +39,8 @@ describe('createLayerDescriptor', () => { { leftField: 'iso2', right: { + applyGlobalQuery: true, + applyGlobalTime: true, id: '12345', indexPatternId: 'apm_static_index_pattern_id', indexPatternTitle: 'apm-*', @@ -176,6 +178,7 @@ describe('createLayerDescriptor', () => { }, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, geoField: 'client.geo.location', id: '12345', indexPatternId: 'apm_static_index_pattern_id', @@ -218,6 +221,7 @@ describe('createLayerDescriptor', () => { }, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, geoField: 'client.geo.location', id: '12345', indexPatternId: 'apm_static_index_pattern_id', 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 bdd86d78b5300..5dbf07ed2a535 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 @@ -177,6 +177,8 @@ export function createLayerDescriptor({ term: 'client.geo.country_iso_code', metrics: [metricsDescriptor], whereQuery: apmSourceQuery, + applyGlobalQuery: true, + applyGlobalTime: true, }, }, ], diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts index 22456527491eb..fe04678deacae 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts @@ -33,6 +33,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'client.geo.location', id: '12345', @@ -140,6 +141,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'server.geo.location', id: '12345', @@ -247,6 +249,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, destGeoField: 'server.geo.location', id: '12345', indexPatternId: 'id', @@ -366,6 +369,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'source.geo.location', id: '12345', @@ -473,6 +477,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, filterByMapBounds: true, geoField: 'destination.geo.location', id: '12345', @@ -580,6 +585,7 @@ describe('createLayerDescriptor', () => { minZoom: 0, sourceDescriptor: { applyGlobalQuery: true, + applyGlobalTime: true, destGeoField: 'destination.geo.location', id: '12345', indexPatternId: 'id', diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index b9d7834896245..b4c0098bb1338 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -254,6 +254,7 @@ export class VectorLayer extends AbstractLayer { timeFilters: searchFilters.timeFilters, filters: searchFilters.filters, applyGlobalQuery: searchFilters.applyGlobalQuery, + applyGlobalTime: searchFilters.applyGlobalTime, }; let bounds = null; @@ -315,6 +316,22 @@ export class VectorLayer extends AbstractLayer { return indexPatternIds; } + async isFilteredByGlobalTime(): Promise { + if (this.getSource().getApplyGlobalTime() && (await this.getSource().isTimeAware())) { + return true; + } + + const joinPromises = this.getValidJoins().map(async (join) => { + return ( + join.getRightJoinSource().getApplyGlobalTime() && + (await join.getRightJoinSource().isTimeAware()) + ); + }); + return (await Promise.all(joinPromises)).some((isJoinTimeAware: boolean) => { + return isJoinTimeAware; + }); + } + async _syncJoin({ join, startLoading, @@ -331,6 +348,7 @@ export class VectorLayer extends AbstractLayer { fieldNames: joinSource.getFieldNames(), sourceQuery: joinSource.getWhereQuery(), applyGlobalQuery: joinSource.getApplyGlobalQuery(), + applyGlobalTime: joinSource.getApplyGlobalTime(), }; const prevDataRequest = this.getDataRequest(sourceDataId); @@ -403,6 +421,7 @@ export class VectorLayer extends AbstractLayer { geogridPrecision: source.getGeoGridPrecision(dataFilters.zoom), sourceQuery: sourceQuery ? sourceQuery : undefined, applyGlobalQuery: source.getApplyGlobalQuery(), + applyGlobalTime: source.getApplyGlobalTime(), sourceMeta: source.getSyncMeta(), }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts index cf0170ab7f1bd..d31e8366e4ef4 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.test.ts @@ -31,7 +31,17 @@ const metricExamples = [ class TestESAggSource extends AbstractESAggSource { constructor(metrics: AggDescriptor[]) { - super({ type: 'test', id: 'foobar', indexPatternId: 'foobarid', metrics }, []); + super( + { + type: 'test', + id: 'foobar', + indexPatternId: 'foobarid', + metrics, + applyGlobalQuery: true, + applyGlobalTime: true, + }, + [] + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 3b1cf3293c0d3..8ac014c820ace 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -151,6 +151,7 @@ describe('ESGeoGridSource', () => { }, extent, applyGlobalQuery: true, + applyGlobalTime: true, fieldNames: [], buffer: extent, sourceQuery: { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index e7099115ffe5e..ec14a80ae761e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -103,6 +103,7 @@ describe('ESSearchSource', () => { }, sourceMeta: null, applyGlobalQuery: true, + applyGlobalTime: true, }; it('Should only include required props', async () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 68b6b131978ea..bef0b8c6ea7af 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -82,8 +82,9 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource type: isValidStringConfig(descriptor.type) ? descriptor.type! : '', indexPatternId: descriptor.indexPatternId!, applyGlobalQuery: - // backfill old _.get usage - typeof descriptor.applyGlobalQuery !== 'undefined' ? !!descriptor.applyGlobalQuery : true, + typeof descriptor.applyGlobalQuery !== 'undefined' ? descriptor.applyGlobalQuery : true, + applyGlobalTime: + typeof descriptor.applyGlobalTime !== 'undefined' ? descriptor.applyGlobalTime : true, }; } @@ -96,6 +97,14 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource return this._descriptor.id; } + getApplyGlobalQuery(): boolean { + return this._descriptor.applyGlobalQuery; + } + + getApplyGlobalTime(): boolean { + return this._descriptor.applyGlobalTime; + } + isFieldAware(): boolean { return true; } @@ -203,10 +212,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource initialSearchContext?: object ): Promise { const indexPattern = await this.getIndexPattern(); - const isTimeAware = await this.isTimeAware(); - const applyGlobalQuery = - typeof searchFilters.applyGlobalQuery === 'boolean' ? searchFilters.applyGlobalQuery : true; - const globalFilters: Filter[] = applyGlobalQuery ? searchFilters.filters : []; + const globalFilters: Filter[] = searchFilters.applyGlobalQuery ? searchFilters.filters : []; const allFilters: Filter[] = [...globalFilters]; if (this.isFilterByMapBounds() && 'buffer' in searchFilters && searchFilters.buffer) { // buffer can be empty @@ -226,7 +232,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource // @ts-expect-error allFilters.push(extentFilter); } - if (isTimeAware) { + if (searchFilters.applyGlobalTime && (await this.isTimeAware())) { const filter = getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters); if (filter) { allFilters.push(filter); @@ -238,7 +244,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource searchSource.setField('index', indexPattern); searchSource.setField('size', limit); searchSource.setField('filter', allFilters); - if (applyGlobalQuery) { + if (searchFilters.applyGlobalQuery) { searchSource.setField('query', searchFilters.query); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts index 3220253436168..8ef50a1cb7a1c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts @@ -28,6 +28,7 @@ import { } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; +import { isValidStringConfig } from '../../util/valid_string_config'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; @@ -48,12 +49,25 @@ export function extractPropertiesMap(rawEsData: any, countPropertyName: string): export class ESTermSource extends AbstractESAggSource { static type = SOURCE_TYPES.ES_TERM_SOURCE; + static createDescriptor(descriptor: Partial): ESTermSourceDescriptor { + const normalizedDescriptor = AbstractESAggSource.createDescriptor(descriptor); + if (!isValidStringConfig(descriptor.term)) { + throw new Error('Cannot create an ESTermSource without a term'); + } + return { + ...normalizedDescriptor, + term: descriptor.term!, + type: SOURCE_TYPES.ES_TERM_SOURCE, + }; + } + private readonly _termField: ESDocField; readonly _descriptor: ESTermSourceDescriptor; constructor(descriptor: ESTermSourceDescriptor, inspectorAdapters: Adapters) { - super(AbstractESAggSource.createDescriptor(descriptor), inspectorAdapters); - this._descriptor = descriptor; + const sourceDescriptor = ESTermSource.createDescriptor(descriptor); + super(sourceDescriptor, inspectorAdapters); + this._descriptor = sourceDescriptor; this._termField = new ESDocField({ fieldName: this._descriptor.term, source: this, diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts index bf39d78a4784f..e6db127e45116 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts @@ -102,10 +102,6 @@ export class KibanaRegionmapSource extends AbstractVectorSource { return this._descriptor.name; } - async isTimeAware() { - return false; - } - canFormatFeatureProperties() { return true; } diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 6390626b006b4..adc478c1f45d3 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -189,10 +189,6 @@ export class MVTSingleLayerVectorSource return null; } - getApplyGlobalQuery(): boolean { - return false; - } - isBoundsAware() { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index f24ec012836b6..7a52c4e6fde20 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -60,6 +60,7 @@ export interface ISource { cloneDescriptor(): AbstractSourceDescriptor; getFieldNames(): string[]; getApplyGlobalQuery(): boolean; + getApplyGlobalTime(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getGeoGridPrecision(zoom: number): number; @@ -135,7 +136,11 @@ export class AbstractSource implements ISource { } getApplyGlobalQuery(): boolean { - return !!this._descriptor.applyGlobalQuery; + return false; + } + + getApplyGlobalTime(): boolean { + return false; } getIndexPatternIds(): string[] { diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 38ff3b49a87f4..32db97708e397 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -37,6 +37,7 @@ export interface GeoJsonWithMeta { export interface BoundsFilters { applyGlobalQuery: boolean; + applyGlobalTime: boolean; filters: Filter[]; query?: MapQuery; sourceQuery?: MapQuery; @@ -61,7 +62,6 @@ export interface IVectorSource extends ISource { getLeftJoinFields(): Promise; getSyncMeta(): VectorSourceSyncMeta | null; getFieldNames(): string[]; - getApplyGlobalQuery(): boolean; createField({ fieldName }: { fieldName: string }): IField; canFormatFeatureProperties(): boolean; getSupportedShapeTypes(): Promise; diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts index 147870dbef371..a7919ad058e4b 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.ts @@ -84,9 +84,13 @@ export async function canSkipSourceUpdate({ return false; } + let updateDueToApplyGlobalTime = false; let updateDueToTime = false; if (timeAware) { - updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); + updateDueToApplyGlobalTime = prevMeta.applyGlobalTime !== nextMeta.applyGlobalTime; + if (nextMeta.applyGlobalTime) { + updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); + } } let updateDueToRefreshTimer = false; @@ -132,6 +136,7 @@ export async function canSkipSourceUpdate({ const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta); return ( + !updateDueToApplyGlobalTime && !updateDueToTime && !updateDueToRefreshTimer && !updateDueToExtentChange && diff --git a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx new file mode 100644 index 0000000000000..b224ad6474745 --- /dev/null +++ b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx @@ -0,0 +1,32 @@ +/* + * 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 React from 'react'; +import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; + +interface Props { + applyGlobalTime: boolean; + label: string; + setApplyGlobalTime: (applyGlobalTime: boolean) => void; +} + +export function GlobalTimeCheckbox({ applyGlobalTime, label, setApplyGlobalTime }: Props) { + const onApplyGlobalTimeChange = (event: EuiSwitchEvent) => { + setApplyGlobalTime(event.target.checked); + }; + + return ( + + + + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js index d2652fac5bd2c..70a04ec37860d 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js @@ -22,23 +22,26 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; +import { GlobalTimeCheckbox } from '../../../components/global_time_checkbox'; export class FilterEditor extends Component { state = { isPopoverOpen: false, indexPatterns: [], + isSourceTimeAware: false, }; componentDidMount() { this._isMounted = true; this._loadIndexPatterns(); + this._loadSourceTimeAware(); } componentWillUnmount() { this._isMounted = false; } - _loadIndexPatterns = async () => { + async _loadIndexPatterns() { // Filter only effects source so only load source indices. const indexPatternIds = this.props.layer.getSource().getIndexPatternIds(); const indexPatterns = []; @@ -58,7 +61,14 @@ export class FilterEditor extends Component { } this.setState({ indexPatterns }); - }; + } + + async _loadSourceTimeAware() { + const isSourceTimeAware = await this.props.layer.getSource().isTimeAware(); + if (this._isMounted) { + this.setState({ isSourceTimeAware }); + } + } _toggle = () => { this.setState((prevState) => ({ @@ -79,6 +89,10 @@ export class FilterEditor extends Component { this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalQuery', applyGlobalQuery); }; + _onApplyGlobalTimeChange = (applyGlobalTime) => { + this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalTime', applyGlobalTime); + }; + _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); const { SearchBar } = getData().ui; @@ -165,6 +179,15 @@ export class FilterEditor extends Component { } render() { + const globalTimeCheckbox = this.state.isSourceTimeAware ? ( + + ) : null; return ( @@ -191,6 +214,8 @@ export class FilterEditor extends Component { applyGlobalQuery={this.props.layer.getSource().getApplyGlobalQuery()} setApplyGlobalQuery={this._onApplyGlobalQueryChange} /> + + {globalTimeCheckbox} ); } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx index 2065668858e22..2d14ba20047bc 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx @@ -66,6 +66,7 @@ export function JoinEditor({ joins, layer, onChange, leftJoinFields, layerDispla right: { id: uuid(), applyGlobalQuery: true, + applyGlobalTime: true, }, } as JoinDescriptor, ]); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js index 5b44801eb780d..54c2b908d1536 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js @@ -12,6 +12,7 @@ import { JoinExpression } from './join_expression'; import { MetricsExpression } from './metrics_expression'; import { WhereExpression } from './where_expression'; import { GlobalFilterCheckbox } from '../../../../components/global_filter_checkbox'; +import { GlobalTimeCheckbox } from '../../../../components/global_time_checkbox'; import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; @@ -126,6 +127,16 @@ export class Join extends Component { }); }; + _onApplyGlobalTimeChange = (applyGlobalTime) => { + this.props.onChange({ + leftField: this.props.join.leftField, + right: { + ...this.props.join.right, + applyGlobalTime, + }, + }); + }; + render() { const { join, onRemove, leftFields, leftSourceName } = this.props; const { rightFields, indexPattern } = this.state; @@ -137,6 +148,7 @@ export class Join extends Component { let metricsExpression; let globalFilterCheckbox; + let globalTimeCheckbox; if (isJoinConfigComplete) { metricsExpression = ( @@ -148,16 +160,27 @@ export class Join extends Component { ); globalFilterCheckbox = ( - - + ); + if (this.state.indexPattern && this.state.indexPattern.timeFieldName) { + globalTimeCheckbox = ( + - - ); + ); + } } let whereExpression; @@ -194,22 +217,24 @@ export class Join extends Component { {metricsExpression} {whereExpression} - - {globalFilterCheckbox} - - + + {globalFilterCheckbox} + + {globalTimeCheckbox} + + ); } diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap index 456414889c732..ea37e76bc8494 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap @@ -5,49 +5,35 @@ exports[`TOCEntryActionsPopover is rendered 1`] = ` anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -132,49 +118,35 @@ exports[`TOCEntryActionsPopover should disable fit to data when supportsFitToBou anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -259,49 +231,36 @@ exports[`TOCEntryActionsPopover should have "show layer" action when layer is no anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} @@ -386,49 +345,35 @@ exports[`TOCEntryActionsPopover should not show edit actions in read only mode 1 anchorClassName="mapLayTocActions__popoverAnchor" anchorPosition="leftUp" button={ - - simulated tooltip content at zoom: 0 -
- - mockFootnoteIcon - - - simulated footnote at isUsingSearch: true -
- + - - - - mockIcon - - - layer 1 - - - - - mockFootnoteIcon - - - -
+ onClick={[Function]} + /> } className="mapLayTocActions" closePopover={[Function]} diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts index d8d43e1e1b27a..673bebc43582e 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/index.ts @@ -14,15 +14,12 @@ import { cloneLayer, removeLayer, } from '../../../../../../actions'; -import { getMapZoom, isUsingSearch } from '../../../../../../selectors/map_selectors'; import { getIsReadOnly } from '../../../../../../selectors/ui_selectors'; import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; function mapStateToProps(state: MapStoreState) { return { isReadOnly: getIsReadOnly(state), - isUsingSearch: isUsingSearch(state), - zoom: getMapZoom(state), }; } @@ -43,8 +40,5 @@ function mapDispatchToProps(dispatch: ThunkDispatchmockIcon, - tooltipContent: `simulated tooltip content at zoom: ${zoom}`, - footnotes: [ - { - icon: mockFootnoteIcon, - message: `simulated footnote at isUsingSearch: ${isUsingSearch}`, - }, - ], - }; - } } const defaultProps = { @@ -59,11 +46,9 @@ const defaultProps = { fitToBounds: () => {}, isEditButtonDisabled: false, isReadOnly: false, - isUsingSearch: true, layer: new LayerMock(), removeLayer: () => {}, toggleVisible: () => {}, - zoom: 0, }; describe('TOCEntryActionsPopover', () => { diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index b46de57e6a789..4d669dfbe235e 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; -import { EuiButtonEmpty, EuiPopover, EuiContextMenu, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiPopover, EuiContextMenu, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../../../../classes/layers/layer'; +import { TOCEntryButton } from '../toc_entry_button'; interface Props { cloneLayer: (layerId: string) => void; @@ -18,11 +19,9 @@ interface Props { fitToBounds: (layerId: string) => void; isEditButtonDisabled: boolean; isReadOnly: boolean; - isUsingSearch: boolean; layer: ILayer; removeLayer: (layerId: string) => void; toggleVisible: (layerId: string) => void; - zoom: number; } interface State { @@ -82,55 +81,6 @@ export class TOCEntryActionsPopover extends Component { this.props.toggleVisible(this.props.layer.getId()); } - _renderPopoverToggleButton() { - const { icon, tooltipContent, footnotes } = this.props.layer.getIconAndTooltipContent( - this.props.zoom, - this.props.isUsingSearch - ); - - const footnoteIcons = footnotes.map((footnote, index) => { - return ( - - {''} - {footnote.icon} - - ); - }); - const footnoteTooltipContent = footnotes.map((footnote, index) => { - return ( -
- {footnote.icon} {footnote.message} -
- ); - }); - - return ( - - {tooltipContent} - {footnoteTooltipContent} - - } - > - - {icon} - {this.props.displayName} {footnoteIcons} - - - ); - } - _getActionsPanel() { const actionItems = [ { @@ -222,7 +172,14 @@ export class TOCEntryActionsPopover extends Component { + } isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} panelPaddingSize="none" diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts new file mode 100644 index 0000000000000..d83c158d0385f --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { connect } from 'react-redux'; +import { MapStoreState } from '../../../../../../reducers/store'; +import { getMapZoom, isUsingSearch } from '../../../../../../selectors/map_selectors'; +import { TOCEntryButton, StateProps, OwnProps } from './toc_entry_button'; + +function mapStateToProps(state: MapStoreState, ownProps: OwnProps): StateProps { + return { + isUsingSearch: isUsingSearch(state), + zoom: getMapZoom(state), + }; +} + +const connected = connect(mapStateToProps)(TOCEntryButton); +export { connected as TOCEntryButton }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx new file mode 100644 index 0000000000000..87c60278b361a --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -0,0 +1,184 @@ +/* + * 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 React, { Component, Fragment, ReactElement } from 'react'; + +import { EuiButtonEmpty, EuiIcon, EuiToolTip, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ILayer } from '../../../../../../classes/layers/layer'; + +interface Footnote { + icon: ReactElement; + message?: string | null; +} + +interface IconAndTooltipContent { + icon?: ReactElement | null; + tooltipContent?: string | null; + footnotes: Footnote[]; +} + +export interface StateProps { + isUsingSearch: boolean; + zoom: number; +} + +export interface OwnProps { + displayName: string; + escapedDisplayName: string; + layer: ILayer; + onClick: () => void; +} + +type Props = StateProps & OwnProps; + +interface State { + isFilteredByGlobalTime: boolean; +} + +export class TOCEntryButton extends Component { + private _isMounted: boolean = false; + + state: State = { + isFilteredByGlobalTime: false, + }; + + componentDidMount() { + this._isMounted = true; + this._loadIsFilteredByGlobalTime(); + } + + componentDidUpdate() { + this._loadIsFilteredByGlobalTime(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadIsFilteredByGlobalTime() { + const isFilteredByGlobalTime = await this.props.layer.isFilteredByGlobalTime(); + if (this._isMounted && isFilteredByGlobalTime !== this.state.isFilteredByGlobalTime) { + this.setState({ isFilteredByGlobalTime }); + } + } + + getIconAndTooltipContent(): IconAndTooltipContent { + let icon; + let tooltipContent = null; + const footnotes = []; + if (this.props.layer.hasErrors()) { + icon = ( + + ); + tooltipContent = this.props.layer.getErrors(); + } else if (this.props.layer.isLayerLoading()) { + icon = ; + } else if (!this.props.layer.isVisible()) { + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { + defaultMessage: `Layer is hidden.`, + }); + } else if (!this.props.layer.showAtZoomLevel(this.props.zoom)) { + const minZoom = this.props.layer.getMinZoom(); + const maxZoom = this.props.layer.getMaxZoom(); + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { + defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, + values: { minZoom, maxZoom }, + }); + } else { + const customIconAndTooltipContent = this.props.layer.getCustomIconAndTooltipContent(); + if (customIconAndTooltipContent) { + icon = customIconAndTooltipContent.icon; + if (!customIconAndTooltipContent.areResultsTrimmed) { + tooltipContent = customIconAndTooltipContent.tooltipContent; + } else { + footnotes.push({ + icon: , + message: customIconAndTooltipContent.tooltipContent, + }); + } + } + + if (this.props.isUsingSearch && this.props.layer.getQueryableIndexPatternIds().length) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { + defaultMessage: 'Results narrowed by search bar', + }), + }); + } + if (this.state.isFilteredByGlobalTime) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingTimeFilter', { + defaultMessage: 'Results narrowed by time filter', + }), + }); + } + } + + return { + icon, + tooltipContent, + footnotes, + }; + } + + render() { + const { icon, tooltipContent, footnotes } = this.getIconAndTooltipContent(); + + const footnoteIcons = footnotes.map((footnote, index) => { + return ( + + {''} + {footnote.icon} + + ); + }); + const footnoteTooltipContent = footnotes.map((footnote, index) => { + return ( +
+ {footnote.icon} {footnote.message} +
+ ); + }); + + return ( + + {tooltipContent} + {footnoteTooltipContent} + + } + > + + {icon} + {this.props.displayName} {footnoteIcons} + + + ); + } +} From 291c34c84c46a08c53143d66f6bbeb797274e80c Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 10 Nov 2020 10:25:59 -0500 Subject: [PATCH 54/87] [App Search] Added the log retention panel to the Settings page (#82982) * Added LogRententionPanel * i18n * fix axe failure * Update x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.test.ts Co-authored-by: Constance * Apply suggestions from code review Co-authored-by: Constance * PR Feeback: interpolation * Apply suggestions from code review Co-authored-by: Constance Co-authored-by: Constance --- .../log_retention_panel.test.tsx | 187 ++++++++++++++++++ .../log_retention/log_retention_panel.tsx | 110 +++++++++++ .../log_retention/messaging/constants.ts | 119 +++++++++++ .../determine_tooltip_content.test.ts | 168 ++++++++++++++++ .../messaging/determine_tooltip_content.ts | 46 +++++ .../log_retention/messaging/index.test.tsx | 74 +++++++ .../log_retention/messaging/index.tsx | 46 +++++ .../settings/log_retention/messaging/types.ts | 18 ++ .../components/settings/settings.tsx | 19 +- 9 files changed, 782 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx new file mode 100644 index 0000000000000..b4f7e92fac8cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx @@ -0,0 +1,187 @@ +/* + * 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 '../../../../__mocks__/kea.mock'; +import '../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockActions, setMockValues } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { LogRetentionPanel } from './log_retention_panel'; +import { ILogRetention } from './types'; + +describe('', () => { + const actions = { + fetchLogRetention: jest.fn(), + toggleLogRetention: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + it('renders', () => { + const logRetentionPanel = shallow(); + expect(logRetentionPanel.find('[data-test-subj="LogRetentionPanel"]')).toHaveLength(1); + }); + + it('initializes data on mount', () => { + shallow(); + expect(actions.fetchLogRetention).toHaveBeenCalledTimes(1); + }); + + it('renders Analytics switch off when analytics log retention is false in LogRetentionLogic ', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + analytics: { + enabled: false, + }, + }), + }); + + const logRetentionPanel = shallow(); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('checked') + ).toEqual(false); + }); + + it('renders Analytics switch on when analyticsLogRetention is true in LogRetentionLogic ', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + analytics: { + enabled: true, + }, + }), + }); + + const logRetentionPanel = shallow(); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('checked') + ).toEqual(true); + }); + + it('renders API switch off when apiLogRetention is false in LogRetentionLogic ', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + api: { + enabled: false, + }, + }), + }); + + const logRetentionPanel = shallow(); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('checked') + ).toEqual(false); + }); + + it('renders API switch on when apiLogRetention is true in LogRetentionLogic ', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + api: { + enabled: true, + }, + }), + }); + + const logRetentionPanel = shallow(); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('checked') + ).toEqual(true); + }); + + it('enables both switches when isLogRetentionUpdating is false', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({}), + }); + const logRetentionPanel = shallow(); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('disabled') + ).toEqual(false); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('disabled') + ).toEqual(false); + }); + + it('disables both switches when isLogRetentionUpdating is true', () => { + setMockValues({ + isLogRetentionUpdating: true, + logRetention: mockLogRetention({}), + }); + const logRetentionPanel = shallow(); + + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('disabled') + ).toEqual(true); + expect( + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('disabled') + ).toEqual(true); + }); + + it('calls toggleLogRetention when analytics log retention option is changed', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + analytics: { + enabled: false, + }, + }), + }); + const logRetentionPanel = shallow(); + logRetentionPanel + .find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]') + .simulate('change'); + expect(actions.toggleLogRetention).toHaveBeenCalledWith('analytics'); + }); + + it('calls toggleLogRetention when api log retention option is changed', () => { + setMockValues({ + isLogRetentionUpdating: false, + logRetention: mockLogRetention({ + analytics: { + enabled: false, + }, + }), + }); + const logRetentionPanel = shallow(); + logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').simulate('change'); + expect(actions.toggleLogRetention).toHaveBeenCalledWith('api'); + }); +}); + +const mockLogRetention = (logRetention: Partial) => { + const baseLogRetention = { + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }; + + return { + analytics: { + ...baseLogRetention.analytics, + ...logRetention.analytics, + }, + api: { + ...baseLogRetention.api, + ...logRetention.api, + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx new file mode 100644 index 0000000000000..7b8eea061d110 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx @@ -0,0 +1,110 @@ +/* + * 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 React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiLink, EuiSpacer, EuiSwitch, EuiText, EuiTextColor, EuiTitle } from '@elastic/eui'; +import { useActions, useValues } from 'kea'; + +import { LogRetentionLogic } from './log_retention_logic'; +import { AnalyticsLogRetentionMessage, ApiLogRetentionMessage } from './messaging'; +import { ELogRetentionOptions } from './types'; + +export const LogRetentionPanel: React.FC = () => { + const { toggleLogRetention, fetchLogRetention } = useActions(LogRetentionLogic); + + const { logRetention, isLogRetentionUpdating } = useValues(LogRetentionLogic); + + const hasILM = logRetention !== null; + const analyticsLogRetentionSettings = logRetention?.[ELogRetentionOptions.Analytics]; + const apiLogRetentionSettings = logRetention?.[ELogRetentionOptions.API]; + + useEffect(() => { + fetchLogRetention(); + }, []); + + return ( +
+ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.title', { + defaultMessage: 'Log Retention', + })} +

+
+ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.description', { + defaultMessage: 'Manage the default write settings for API Logs and Analytics.', + })}{' '} + + {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore', { + defaultMessage: 'Learn more about retention settings.', + })} + +

+
+ + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label', + { + defaultMessage: 'Analytics Logs', + } + )} + + {': '} + {hasILM && ( + + + + )} + + } + checked={!!analyticsLogRetentionSettings?.enabled} + onChange={() => toggleLogRetention(ELogRetentionOptions.Analytics)} + disabled={isLogRetentionUpdating} + data-test-subj="LogRetentionPanelAnalyticsSwitch" + /> + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.label', + { + defaultMessage: 'API Logs', + } + )} + + {': '} + {hasILM && ( + + + + )} + + } + checked={!!apiLogRetentionSettings?.enabled} + onChange={() => toggleLogRetention(ELogRetentionOptions.API)} + disabled={isLogRetentionUpdating} + data-test-subj="LogRetentionPanelAPISwitch" + /> + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts new file mode 100644 index 0000000000000..f7f0fbdff1acb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/constants.ts @@ -0,0 +1,119 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { ILogRetentionMessages } from './types'; +import { renderLogRetentionDate } from '.'; + +const ANALYTICS_NO_LOGGING = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.noLogging', + { + defaultMessage: 'Analytics collection has been disabled for all engines.', + } +); + +const ANALYTICS_NO_LOGGING_COLLECTED = (disabledAt: string) => + i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.noLogging.collected', + { + defaultMessage: 'The last date analytics were collected was {disabledAt}.', + values: { disabledAt }, + } + ); + +const ANALYTICS_NO_LOGGING_NOT_COLLECTED = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.noLogging.notCollected', + { + defaultMessage: 'There are no analytics collected.', + } +); + +const ANALYTICS_ILM_DISABLED = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.ilmDisabled', + { + defaultMessage: "App Search isn't managing analytics retention.", + } +); + +const ANALYTICS_CUSTOM_POLICY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.customPolicy', + { + defaultMessage: 'You have a custom analytics retention policy.', + } +); + +const ANALYTICS_STORED = (minAgeDays: number | null | undefined) => + i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.stored', { + defaultMessage: 'Your analytics are being stored for at least {minAgeDays} days.', + values: { minAgeDays }, + }); + +const API_NO_LOGGING = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.noLogging', + { + defaultMessage: 'API logging has been disabled for all engines.', + } +); + +const API_NO_LOGGING_COLLECTED = (disabledAt: string) => + i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.api.noLogging.collected', { + defaultMessage: 'The last date logs were collected was {disabledAt}.', + values: { disabledAt }, + }); + +const API_NO_LOGGING_NOT_COLLECTED = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.noLogging.notCollected', + { + defaultMessage: 'There are no logs collected.', + } +); + +const API_ILM_DISABLED = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.ilmDisabled', + { + defaultMessage: "App Search isn't managing API log retention.", + } +); + +const API_CUSTOM_POLICY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.customPolicy', + { + defaultMessage: 'You have a custom API log retention policy.', + } +); + +const API_STORED = (minAgeDays: number | null | undefined) => + i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.api.stored', { + defaultMessage: 'Your logs are being stored for at least {minAgeDays} days.', + values: { minAgeDays }, + }); + +export const ANALYTICS_MESSAGES: ILogRetentionMessages = { + noLogging: (_, logRetentionSettings) => + `${ANALYTICS_NO_LOGGING} ${ + logRetentionSettings.disabledAt + ? ANALYTICS_NO_LOGGING_COLLECTED(renderLogRetentionDate(logRetentionSettings.disabledAt)) + : ANALYTICS_NO_LOGGING_NOT_COLLECTED + }`, + ilmDisabled: ANALYTICS_ILM_DISABLED, + customPolicy: ANALYTICS_CUSTOM_POLICY, + defaultPolicy: (_, logRetentionSettings) => + ANALYTICS_STORED(logRetentionSettings.retentionPolicy?.minAgeDays), +}; + +export const API_MESSAGES: ILogRetentionMessages = { + noLogging: (_, logRetentionSettings) => + `${API_NO_LOGGING} ${ + logRetentionSettings.disabledAt + ? API_NO_LOGGING_COLLECTED(renderLogRetentionDate(logRetentionSettings.disabledAt)) + : API_NO_LOGGING_NOT_COLLECTED + }`, + ilmDisabled: API_ILM_DISABLED, + customPolicy: API_CUSTOM_POLICY, + defaultPolicy: (_, logRetentionSettings) => + API_STORED(logRetentionSettings.retentionPolicy?.minAgeDays), +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.test.ts new file mode 100644 index 0000000000000..fbc2ccfbc8a52 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.test.ts @@ -0,0 +1,168 @@ +/* + * 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 { determineTooltipContent } from './determine_tooltip_content'; +import { ANALYTICS_MESSAGES, API_MESSAGES } from './constants'; + +describe('determineTooltipContent', () => { + const BASE_SETTINGS = { + disabledAt: null, + enabled: true, + retentionPolicy: null, + }; + + it('will return nothing if settings are not provided', () => { + expect(determineTooltipContent(ANALYTICS_MESSAGES, true)).toBeUndefined(); + }); + + describe('analytics messages', () => { + describe('when analytics logs are enabled', () => { + describe("and they're using the default policy", () => { + it('will render a retention policy message', () => { + expect( + determineTooltipContent(ANALYTICS_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: true, + retentionPolicy: { + isDefault: true, + minAgeDays: 7, + }, + }) + ).toEqual('Your analytics are being stored for at least 7 days.'); + }); + }); + + describe('and there is a custom policy', () => { + it('will render a retention policy message', () => { + expect( + determineTooltipContent(ANALYTICS_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: true, + retentionPolicy: { + isDefault: false, + minAgeDays: 7, + }, + }) + ).toEqual('You have a custom analytics retention policy.'); + }); + }); + }); + + describe('when analytics logs are disabled', () => { + describe('and there is no disabledAt date', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(ANALYTICS_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: false, + disabledAt: null, + }) + ).toEqual( + 'Analytics collection has been disabled for all engines. There are no analytics collected.' + ); + }); + }); + + describe('and there is a disabledAt date', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(ANALYTICS_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: false, + disabledAt: 'Thu, 05 Nov 2020 18:57:28 +0000', + }) + ).toEqual( + 'Analytics collection has been disabled for all engines. The last date analytics were collected was November 5, 2020.' + ); + }); + }); + }); + + describe('when ilm is disabled entirely', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(ANALYTICS_MESSAGES, false, { + ...BASE_SETTINGS, + enabled: true, + }) + ).toEqual("App Search isn't managing analytics retention."); + }); + }); + }); + + describe('api messages', () => { + describe('when analytics logs are enabled', () => { + describe("and they're using the default policy", () => { + it('will render a retention policy message', () => { + expect( + determineTooltipContent(API_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: true, + retentionPolicy: { + isDefault: true, + minAgeDays: 7, + }, + }) + ).toEqual('Your logs are being stored for at least 7 days.'); + }); + }); + + describe('and there is a custom policy', () => { + it('will render a retention policy message', () => { + expect( + determineTooltipContent(API_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: true, + retentionPolicy: { + isDefault: false, + minAgeDays: 7, + }, + }) + ).toEqual('You have a custom API log retention policy.'); + }); + }); + }); + + describe('when analytics logs are disabled', () => { + describe('and there is no disabledAt date', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(API_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: false, + disabledAt: null, + }) + ).toEqual('API logging has been disabled for all engines. There are no logs collected.'); + }); + }); + + describe('and there is a disabledAt date', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(API_MESSAGES, true, { + ...BASE_SETTINGS, + enabled: false, + disabledAt: 'Thu, 05 Nov 2020 18:57:28 +0000', + }) + ).toEqual( + 'API logging has been disabled for all engines. The last date logs were collected was November 5, 2020.' + ); + }); + }); + }); + + describe('when ilm is disabled entirely', () => { + it('will render a no logging message', () => { + expect( + determineTooltipContent(API_MESSAGES, false, { + ...BASE_SETTINGS, + enabled: true, + }) + ).toEqual("App Search isn't managing API log retention."); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts new file mode 100644 index 0000000000000..e361e28490a83 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/determine_tooltip_content.ts @@ -0,0 +1,46 @@ +/* + * 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 { ILogRetentionSettings } from '../types'; +import { TMessageStringOrFunction, ILogRetentionMessages } from './types'; + +export const determineTooltipContent = ( + messages: ILogRetentionMessages, + ilmEnabled: boolean, + logRetentionSettings?: ILogRetentionSettings +) => { + if (typeof logRetentionSettings === 'undefined') { + return; + } + + const renderOrReturnMessage = (message: TMessageStringOrFunction) => { + if (typeof message === 'function') { + return message(ilmEnabled, logRetentionSettings); + } + return message; + }; + + if (!logRetentionSettings.enabled) { + return renderOrReturnMessage(messages.noLogging); + } + if (logRetentionSettings.enabled && !ilmEnabled) { + return renderOrReturnMessage(messages.ilmDisabled); + } + if ( + logRetentionSettings.enabled && + ilmEnabled && + !logRetentionSettings.retentionPolicy?.isDefault + ) { + return renderOrReturnMessage(messages.customPolicy); + } + if ( + logRetentionSettings.enabled && + ilmEnabled && + logRetentionSettings.retentionPolicy?.isDefault + ) { + return renderOrReturnMessage(messages.defaultPolicy); + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.test.tsx new file mode 100644 index 0000000000000..b65ffc04ad700 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.test.tsx @@ -0,0 +1,74 @@ +/* + * 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 '../../../../../__mocks__/kea.mock'; +import { setMockValues } from '../../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { AnalyticsLogRetentionMessage, ApiLogRetentionMessage, renderLogRetentionDate } from '.'; + +describe('LogRetentionMessaging', () => { + const LOG_RETENTION = { + analytics: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + api: { + disabledAt: null, + enabled: true, + retentionPolicy: { isDefault: true, minAgeDays: 180 }, + }, + }; + + describe('renderLogRetentionDate', () => { + it('renders a formatted date', () => { + expect(renderLogRetentionDate('Thu, 05 Nov 2020 18:57:28 +0000')).toEqual('November 5, 2020'); + }); + }); + + describe('AnalyticsLogRetentionMessage', () => { + it('renders', () => { + setMockValues({ + ilmEnabled: true, + logRetention: LOG_RETENTION, + }); + const wrapper = shallow(); + expect(wrapper.text()).toEqual('Your analytics are being stored for at least 180 days.'); + }); + + it('renders nothing if logRetention is null', () => { + setMockValues({ + ilmEnabled: true, + logRetention: null, + }); + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toEqual(true); + }); + }); + + describe('ApiLogRetentionMessage', () => { + it('renders', () => { + setMockValues({ + ilmEnabled: true, + logRetention: LOG_RETENTION, + }); + const wrapper = shallow(); + expect(wrapper.text()).toEqual('Your logs are being stored for at least 180 days.'); + }); + + it('renders nothing if logRetention is null', () => { + setMockValues({ + ilmEnabled: true, + logRetention: null, + }); + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx new file mode 100644 index 0000000000000..9fe2e8dc72ed6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/index.tsx @@ -0,0 +1,46 @@ +/* + * 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 React from 'react'; + +import { useValues } from 'kea'; +import moment from 'moment'; + +import { AppLogic } from '../../../../app_logic'; +import { LogRetentionLogic } from '../log_retention_logic'; +import { ELogRetentionOptions } from '../types'; + +import { determineTooltipContent } from './determine_tooltip_content'; +import { ANALYTICS_MESSAGES, API_MESSAGES } from './constants'; + +export const renderLogRetentionDate = (dateString: string) => + moment(dateString).format('MMMM D, YYYY'); + +export const AnalyticsLogRetentionMessage: React.FC = () => { + const { ilmEnabled } = useValues(AppLogic); + const { logRetention } = useValues(LogRetentionLogic); + if (!logRetention) return null; + + return ( + <> + {determineTooltipContent( + ANALYTICS_MESSAGES, + ilmEnabled, + logRetention[ELogRetentionOptions.Analytics] + )} + + ); +}; + +export const ApiLogRetentionMessage: React.FC = () => { + const { ilmEnabled } = useValues(AppLogic); + const { logRetention } = useValues(LogRetentionLogic); + if (!logRetention) return null; + + return ( + <>{determineTooltipContent(API_MESSAGES, ilmEnabled, logRetention[ELogRetentionOptions.API])} + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts new file mode 100644 index 0000000000000..a9546343d9aa7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/messaging/types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ILogRetentionSettings } from '../types'; + +export type TMessageStringOrFunction = + | string + | ((ilmEnabled: boolean, logRetentionSettings: ILogRetentionSettings) => string); + +export interface ILogRetentionMessages { + noLogging: TMessageStringOrFunction; + ilmDisabled: TMessageStringOrFunction; + customPolicy: TMessageStringOrFunction; + defaultPolicy: TMessageStringOrFunction; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx index 13079bb380f13..7bd3683919bc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx @@ -5,11 +5,17 @@ */ import React from 'react'; - -import { EuiPageHeader, EuiPageHeaderSection, EuiPageContentBody, EuiTitle } from '@elastic/eui'; +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContent, + EuiPageContentBody, + EuiTitle, +} from '@elastic/eui'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { FlashMessages } from '../../../shared/flash_messages'; +import { LogRetentionPanel } from './log_retention/log_retention_panel'; import { SETTINGS_TITLE } from './'; @@ -24,9 +30,12 @@ export const Settings: React.FC = () => { - - - + + + + + + ); }; From 00ca555cd98b058420898498114666b443b0ccbf Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Tue, 10 Nov 2020 10:50:31 -0500 Subject: [PATCH 55/87] [Security Solution][Detections] - follow up cleanup on auto refresh rules (#83023) ### Summary This is a follow up cleanup PR to #82062 . There were a few comments I hadn't gotten to and wanted to follow up on. --- .../cypress/tasks/alerts_detection_rules.ts | 1 - .../detection_engine/rules/all/index.tsx | 19 +++++++------------ .../rules/all/utility_bar.test.tsx | 12 +++++++----- .../security_solution/server/ui_settings.ts | 17 +++++++++-------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index d4602dcd16db8..bcee151a6be42 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -125,7 +125,6 @@ export const waitForRulesToBeLoaded = () => { cy.get(ASYNC_LOADING_PROGRESS).should('not.exist'); }; -// when using, ensure you've called cy.clock prior in test export const checkAutoRefresh = (ms: number, condition: string) => { cy.get(ASYNC_LOADING_PROGRESS).should('not.be.visible'); cy.tick(ms); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx index 663a4bb242c06..dcf9765c0cdd1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx @@ -13,6 +13,7 @@ import { EuiProgress, EuiOverlayMask, EuiConfirmModal, + EuiWindowEvent, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; @@ -406,18 +407,6 @@ export const AllRules = React.memo( [setAutoRefreshOn, handleRefreshData] ); - useEffect(() => { - debounceResetIdleTimer(); - - window.addEventListener('mousemove', debounceResetIdleTimer, { passive: true }); - window.addEventListener('keydown', debounceResetIdleTimer); - - return () => { - window.removeEventListener('mousemove', debounceResetIdleTimer); - window.removeEventListener('keydown', debounceResetIdleTimer); - }; - }, [handleResetIdleTimer, debounceResetIdleTimer]); - const shouldShowRulesTable = useMemo( (): boolean => showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading, [initLoading, rulesCustomInstalled, rulesInstalled] @@ -470,6 +459,12 @@ export const AllRules = React.memo( return ( <> + + + + + + ({ eui: euiDarkVars, darkMode: true }); + describe('AllRules', () => { it('renders AllRulesUtilityBar total rules and selected rules', () => { const wrapper = mount( - ({ eui: euiDarkVars, darkMode: true })}> + { it('renders utility actions if user has permissions', () => { const wrapper = mount( - ({ eui: euiDarkVars, darkMode: true })}> + { it('renders no utility actions if user has no permissions', () => { const wrapper = mount( - ({ eui: euiDarkVars, darkMode: true })}> + { it('invokes refresh on refresh action click', () => { const mockRefresh = jest.fn(); const wrapper = mount( - ({ eui: euiDarkVars, darkMode: true })}> + { it('invokes onRefreshSwitch when auto refresh switch is clicked', async () => { const mockSwitch = jest.fn(); const wrapper = mount( - ({ eui: euiDarkVars, darkMode: true })}> + { '

Default refresh interval for the Security time filter, in milliseconds.

', } ), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.object({ value: schema.number(), @@ -66,7 +67,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { description: i18n.translate('xpack.securitySolution.uiSettings.defaultTimeRangeDescription', { defaultMessage: '

Default period of time in the Security time filter.

', }), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.object({ from: schema.string(), @@ -82,7 +83,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { defaultMessage: '

Comma-delimited list of Elasticsearch indices from which the Security app collects events.

', }), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.arrayOf(schema.string()), }, @@ -99,7 +100,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { '

Value above which Machine Learning job anomalies are displayed in the Security app.

Valid values: 0 to 100.

', } ), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.number(), }, @@ -112,7 +113,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { defaultMessage: '

Enables the News feed

', }), type: 'boolean', - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.boolean(), }, @@ -133,7 +134,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { "value": ${DEFAULT_RULE_REFRESH_INTERVAL_VALUE}, "idleTimeout": ${DEFAULT_RULE_REFRESH_IDLE_VALUE} }`, - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.object({ idleTimeout: schema.number({ min: 300000 }), @@ -149,7 +150,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { description: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrlDescription', { defaultMessage: '

News feed content will be retrieved from this URL

', }), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.string(), }, @@ -166,7 +167,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { 'Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.', } ), - category: ['securitySolution'], + category: [APP_ID], requiresPageReload: true, schema: schema.arrayOf( schema.object({ From 4dba10c76a7f5daead0650030d84393d65f65c06 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Tue, 10 Nov 2020 09:51:27 -0600 Subject: [PATCH 56/87] Adds cloud links to user menu (#82803) Co-authored-by: Ryan Keairns --- x-pack/plugins/cloud/kibana.json | 2 +- x-pack/plugins/cloud/public/index.ts | 2 +- x-pack/plugins/cloud/public/mocks.ts | 18 +++ x-pack/plugins/cloud/public/plugin.ts | 28 +++- .../plugins/cloud/public/user_menu_links.ts | 38 +++++ x-pack/plugins/cloud/server/config.ts | 2 + x-pack/plugins/security/public/index.ts | 1 + x-pack/plugins/security/public/mocks.ts | 7 + .../security/public/nav_control/index.mock.ts | 14 ++ .../security/public/nav_control/index.ts | 3 +- .../nav_control/nav_control_component.scss | 11 ++ .../nav_control_component.test.tsx | 38 +++++ .../nav_control/nav_control_component.tsx | 139 ++++++++++++------ .../nav_control/nav_control_service.test.ts | 130 ++++++++++++++++ .../nav_control/nav_control_service.tsx | 41 +++++- .../plugins/security/public/plugin.test.tsx | 7 +- x-pack/plugins/security/public/plugin.tsx | 4 +- 17 files changed, 424 insertions(+), 61 deletions(-) create mode 100644 x-pack/plugins/cloud/public/mocks.ts create mode 100644 x-pack/plugins/cloud/public/user_menu_links.ts create mode 100644 x-pack/plugins/security/public/nav_control/index.mock.ts create mode 100644 x-pack/plugins/security/public/nav_control/nav_control_component.scss diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 27b35bcbdd88b..9bca2f30bd23c 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["usageCollection", "home"], + "optionalPlugins": ["usageCollection", "home", "security"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index 39ef5f452c18b..680b2f1ad2bd6 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -7,7 +7,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { CloudPlugin } from './plugin'; -export { CloudSetup } from './plugin'; +export { CloudSetup, CloudConfigType } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } diff --git a/x-pack/plugins/cloud/public/mocks.ts b/x-pack/plugins/cloud/public/mocks.ts new file mode 100644 index 0000000000000..bafebbca4ecdd --- /dev/null +++ b/x-pack/plugins/cloud/public/mocks.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +function createSetupMock() { + return { + cloudId: 'mock-cloud-id', + isCloudEnabled: true, + resetPasswordUrl: 'reset-password-url', + accountUrl: 'account-url', + }; +} + +export const cloudMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 45005f3f5e422..bc410b89c30e7 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -6,40 +6,51 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import { SecurityPluginStart } from '../../security/public'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { ELASTIC_SUPPORT_LINK } from '../common/constants'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; +import { createUserMenuLinks } from './user_menu_links'; -interface CloudConfigType { +export interface CloudConfigType { id?: string; resetPasswordUrl?: string; deploymentUrl?: string; + accountUrl?: string; } interface CloudSetupDependencies { home?: HomePublicPluginSetup; } +interface CloudStartDependencies { + security?: SecurityPluginStart; +} + export interface CloudSetup { cloudId?: string; cloudDeploymentUrl?: string; isCloudEnabled: boolean; + resetPasswordUrl?: string; + accountUrl?: string; } export class CloudPlugin implements Plugin { private config!: CloudConfigType; + private isCloudEnabled: boolean; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); + this.isCloudEnabled = false; } public async setup(core: CoreSetup, { home }: CloudSetupDependencies) { const { id, resetPasswordUrl, deploymentUrl } = this.config; - const isCloudEnabled = getIsCloudEnabled(id); + this.isCloudEnabled = getIsCloudEnabled(id); if (home) { - home.environment.update({ cloud: isCloudEnabled }); - if (isCloudEnabled) { + home.environment.update({ cloud: this.isCloudEnabled }); + if (this.isCloudEnabled) { home.tutorials.setVariable('cloud', { id, resetPasswordUrl }); } } @@ -47,11 +58,11 @@ export class CloudPlugin implements Plugin { return { cloudId: id, cloudDeploymentUrl: deploymentUrl, - isCloudEnabled, + isCloudEnabled: this.isCloudEnabled, }; } - public start(coreStart: CoreStart) { + public start(coreStart: CoreStart, { security }: CloudStartDependencies) { const { deploymentUrl } = this.config; coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK); if (deploymentUrl) { @@ -63,5 +74,10 @@ export class CloudPlugin implements Plugin { href: deploymentUrl, }); } + + if (security && this.isCloudEnabled) { + const userMenuLinks = createUserMenuLinks(this.config); + security.navControlService.addUserMenuLinks(userMenuLinks); + } } } diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts new file mode 100644 index 0000000000000..15e2f14e885ba --- /dev/null +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { UserMenuLink } from '../../security/public'; +import { CloudConfigType } from '.'; + +export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => { + const { resetPasswordUrl, accountUrl } = config; + const userMenuLinks = [] as UserMenuLink[]; + + if (resetPasswordUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { + defaultMessage: 'Cloud profile', + }), + iconType: 'logoCloud', + href: resetPasswordUrl, + order: 100, + }); + } + + if (accountUrl) { + userMenuLinks.push({ + label: i18n.translate('xpack.cloud.userMenuLinks.accountLinkText', { + defaultMessage: 'Account & Billing', + }), + iconType: 'gear', + href: accountUrl, + order: 200, + }); + } + + return userMenuLinks; +}; diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index ff8a2c5acdf9a..eaa4ab7a482dd 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -23,6 +23,7 @@ const configSchema = schema.object({ apm: schema.maybe(apmConfigSchema), resetPasswordUrl: schema.maybe(schema.string()), deploymentUrl: schema.maybe(schema.string()), + accountUrl: schema.maybe(schema.string()), }); export type CloudConfigType = TypeOf; @@ -32,6 +33,7 @@ export const config: PluginConfigDescriptor = { id: true, resetPasswordUrl: true, deploymentUrl: true, + accountUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index 8016c94224060..d0382c22ed3c6 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -16,6 +16,7 @@ import { export { SecurityPluginSetup, SecurityPluginStart }; export { AuthenticatedUser } from '../common/model'; export { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; +export { UserMenuLink } from '../public/nav_control'; export const plugin: PluginInitializer< SecurityPluginSetup, diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts index 33c1d1446afba..26a759ca52267 100644 --- a/x-pack/plugins/security/public/mocks.ts +++ b/x-pack/plugins/security/public/mocks.ts @@ -7,6 +7,7 @@ import { authenticationMock } from './authentication/index.mock'; import { createSessionTimeoutMock } from './session/session_timeout.mock'; import { licenseMock } from '../common/licensing/index.mock'; +import { navControlServiceMock } from './nav_control/index.mock'; function createSetupMock() { return { @@ -15,7 +16,13 @@ function createSetupMock() { license: licenseMock.create(), }; } +function createStartMock() { + return { + navControlService: navControlServiceMock.createStart(), + }; +} export const securityMock = { createSetup: createSetupMock, + createStart: createStartMock, }; diff --git a/x-pack/plugins/security/public/nav_control/index.mock.ts b/x-pack/plugins/security/public/nav_control/index.mock.ts new file mode 100644 index 0000000000000..1cd10810d7c8f --- /dev/null +++ b/x-pack/plugins/security/public/nav_control/index.mock.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SecurityNavControlServiceStart } from '.'; + +export const navControlServiceMock = { + createStart: (): jest.Mocked => ({ + getUserMenuLinks$: jest.fn(), + addUserMenuLinks: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/nav_control/index.ts b/x-pack/plugins/security/public/nav_control/index.ts index 2b0af1a45d05a..737ae50054698 100644 --- a/x-pack/plugins/security/public/nav_control/index.ts +++ b/x-pack/plugins/security/public/nav_control/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SecurityNavControlService } from './nav_control_service'; +export { SecurityNavControlService, SecurityNavControlServiceStart } from './nav_control_service'; +export { UserMenuLink } from './nav_control_component'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.scss b/x-pack/plugins/security/public/nav_control/nav_control_component.scss new file mode 100644 index 0000000000000..a3e04b08cfac2 --- /dev/null +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.scss @@ -0,0 +1,11 @@ +.chrNavControl__userMenu { + .euiContextMenuPanelTitle { + // Uppercased by default, override to match actual username + text-transform: none; + } + + .euiContextMenuItem { + // Temp fix for EUI issue https://github.com/elastic/eui/issues/3092 + line-height: normal; + } +} \ No newline at end of file diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index c1c6a9f69b6ec..1da91e80d062d 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { BehaviorSubject } from 'rxjs'; import { shallowWithIntl, nextTick, mountWithIntl } from 'test_utils/enzyme_helpers'; import { SecurityNavControl } from './nav_control_component'; import { AuthenticatedUser } from '../../common/model'; @@ -17,6 +18,7 @@ describe('SecurityNavControl', () => { user: new Promise(() => {}) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -42,6 +44,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = shallowWithIntl(); @@ -70,6 +73,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -91,6 +95,7 @@ describe('SecurityNavControl', () => { user: Promise.resolve({ full_name: 'foo' }) as Promise, editProfileUrl: '', logoutUrl: '', + userMenuLinks$: new BehaviorSubject([]), }; const wrapper = mountWithIntl(); @@ -107,4 +112,37 @@ describe('SecurityNavControl', () => { expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); }); + + it('renders a popover with additional user menu links registered by other plugins', async () => { + const props = { + user: Promise.resolve({ full_name: 'foo' }) as Promise, + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, + { label: 'link3', href: 'path-to-link-3', iconType: 'empty', order: 3 }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); + expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(0); + expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(0); + expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); + expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(1); + expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(1); + expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 3ddabb0dc55f8..c22308fa8a43e 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -7,38 +7,52 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; - +import { Observable, Subscription } from 'rxjs'; import { EuiAvatar, - EuiFlexGroup, - EuiFlexItem, EuiHeaderSectionItemButton, - EuiLink, - EuiText, - EuiSpacer, EuiPopover, EuiLoadingSpinner, + EuiIcon, + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, + IconType, + EuiText, } from '@elastic/eui'; import { AuthenticatedUser } from '../../common/model'; +import './nav_control_component.scss'; + +export interface UserMenuLink { + label: string; + iconType: IconType; + href: string; + order?: number; +} + interface Props { user: Promise; editProfileUrl: string; logoutUrl: string; + userMenuLinks$: Observable; } interface State { isOpen: boolean; authenticatedUser: AuthenticatedUser | null; + userMenuLinks: UserMenuLink[]; } export class SecurityNavControl extends Component { + private subscription?: Subscription; + constructor(props: Props) { super(props); this.state = { isOpen: false, authenticatedUser: null, + userMenuLinks: [], }; props.user.then((authenticatedUser) => { @@ -48,6 +62,18 @@ export class SecurityNavControl extends Component { }); } + componentDidMount() { + this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { + this.setState({ userMenuLinks }); + }); + } + + componentWillUnmount() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + onMenuButtonClick = () => { if (!this.state.authenticatedUser) { return; @@ -66,13 +92,13 @@ export class SecurityNavControl extends Component { render() { const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser } = this.state; + const { authenticatedUser, userMenuLinks } = this.state; - const name = + const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; const buttonContents = authenticatedUser ? ( - + ) : ( ); @@ -92,6 +118,60 @@ export class SecurityNavControl extends Component { ); + const profileMenuItem = { + name: ( + + ), + icon: , + href: editProfileUrl, + 'data-test-subj': 'profileLink', + }; + + const logoutMenuItem = { + name: ( + + ), + icon: , + href: logoutUrl, + 'data-test-subj': 'logoutLink', + }; + + const items: EuiContextMenuPanelItemDescriptor[] = []; + + items.push(profileMenuItem); + + if (userMenuLinks.length) { + const userMenuLinkMenuItems = userMenuLinks + .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) + .map(({ label, iconType, href }: UserMenuLink) => ({ + name: {label}, + icon: , + href, + 'data-test-subj': `userMenuLink__${label}`, + })); + + items.push(...userMenuLinkMenuItems, { + isSeparator: true, + key: 'securityNavControlComponent__userMenuLinksSeparator', + }); + } + + items.push(logoutMenuItem); + + const panels = [ + { + id: 0, + title: username, + items, + }, + ]; + return ( { repositionOnScroll closePopover={this.closeMenu} panelPaddingSize="none" + buffer={0} > -
- - - - - - - -

{name}

-
- - - - - - - - - - - - - - - - - - - - -
-
+
+
); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index acf62f3376b8b..5b9788d67500b 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -173,4 +173,134 @@ describe('SecurityNavControlService', () => { navControlService.start({ core: coreStart }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(2); }); + + describe(`#start`, () => { + it('should return functions to register and retrieve user menu links', () => { + const license$ = new BehaviorSubject(validLicense); + + const navControlService = new SecurityNavControlService(); + navControlService.setup({ + securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, + logoutUrl: '/some/logout/url', + }); + + const coreStart = coreMock.createStart(); + const navControlServiceStart = navControlService.start({ core: coreStart }); + expect(navControlServiceStart).toHaveProperty('getUserMenuLinks$'); + expect(navControlServiceStart).toHaveProperty('addUserMenuLinks'); + }); + + it('should register custom user menu links to be displayed in the nav controls', (done) => { + const license$ = new BehaviorSubject(validLicense); + + const navControlService = new SecurityNavControlService(); + navControlService.setup({ + securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, + logoutUrl: '/some/logout/url', + }); + + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + addUserMenuLinks([ + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + }, + ]); + + userMenuLinks$.subscribe((links) => { + expect(links).toMatchInlineSnapshot(` + Array [ + Object { + "href": "path-to-link1", + "iconType": "empty", + "label": "link1", + }, + ] + `); + done(); + }); + }); + + it('should retrieve user menu links sorted by order', (done) => { + const license$ = new BehaviorSubject(validLicense); + + const navControlService = new SecurityNavControlService(); + navControlService.setup({ + securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, + logoutUrl: '/some/logout/url', + }); + + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + addUserMenuLinks([ + { + label: 'link3', + href: 'path-to-link3', + iconType: 'empty', + order: 3, + }, + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + order: 1, + }, + { + label: 'link2', + href: 'path-to-link2', + iconType: 'empty', + order: 2, + }, + ]); + addUserMenuLinks([ + { + label: 'link4', + href: 'path-to-link4', + iconType: 'empty', + order: 4, + }, + ]); + + userMenuLinks$.subscribe((links) => { + expect(links).toMatchInlineSnapshot(` + Array [ + Object { + "href": "path-to-link1", + "iconType": "empty", + "label": "link1", + "order": 1, + }, + Object { + "href": "path-to-link2", + "iconType": "empty", + "label": "link2", + "order": 2, + }, + Object { + "href": "path-to-link3", + "iconType": "empty", + "label": "link3", + "order": 3, + }, + Object { + "href": "path-to-link4", + "iconType": "empty", + "label": "link4", + "order": 4, + }, + ] + `); + done(); + }); + }); + }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index aa3ec2e47469d..5d2e7d7dfb733 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subscription } from 'rxjs'; +import { sortBy } from 'lodash'; +import { Observable, Subscription, BehaviorSubject, ReplaySubject } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; import { CoreStart } from 'src/core/public'; + import ReactDOM from 'react-dom'; import React from 'react'; + import { SecurityLicense } from '../../common/licensing'; -import { SecurityNavControl } from './nav_control_component'; +import { SecurityNavControl, UserMenuLink } from './nav_control_component'; import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { @@ -22,6 +26,18 @@ interface StartDeps { core: CoreStart; } +export interface SecurityNavControlServiceStart { + /** + * Returns an Observable of the array of user menu links registered by other plugins + */ + getUserMenuLinks$: () => Observable; + + /** + * Registers the provided user menu links to be displayed in the user menu in the global nav + */ + addUserMenuLinks: (newUserMenuLink: UserMenuLink[]) => void; +} + export class SecurityNavControlService { private securityLicense!: SecurityLicense; private authc!: AuthenticationServiceSetup; @@ -31,13 +47,16 @@ export class SecurityNavControlService { private securityFeaturesSubscription?: Subscription; + private readonly stop$ = new ReplaySubject(1); + private userMenuLinks$ = new BehaviorSubject([]); + public setup({ securityLicense, authc, logoutUrl }: SetupDeps) { this.securityLicense = securityLicense; this.authc = authc; this.logoutUrl = logoutUrl; } - public start({ core }: StartDeps) { + public start({ core }: StartDeps): SecurityNavControlServiceStart { this.securityFeaturesSubscription = this.securityLicense.features$.subscribe( ({ showLinks }) => { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); @@ -49,6 +68,16 @@ export class SecurityNavControlService { } } ); + + return { + getUserMenuLinks$: () => + this.userMenuLinks$.pipe(map(this.sortUserMenuLinks), takeUntil(this.stop$)), + addUserMenuLinks: (userMenuLinks: UserMenuLink[]) => { + const currentLinks = this.userMenuLinks$.value; + const newLinks = [...currentLinks, ...userMenuLinks]; + this.userMenuLinks$.next(newLinks); + }, + }; } public stop() { @@ -57,6 +86,7 @@ export class SecurityNavControlService { this.securityFeaturesSubscription = undefined; } this.navControlRegistered = false; + this.stop$.next(); } private registerSecurityNavControl( @@ -72,6 +102,7 @@ export class SecurityNavControlService { user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/security/account'), logoutUrl: this.logoutUrl, + userMenuLinks$: this.userMenuLinks$, }; ReactDOM.render( @@ -86,4 +117,8 @@ export class SecurityNavControlService { this.navControlRegistered = true; } + + private sortUserMenuLinks(userMenuLinks: UserMenuLink[]) { + return sortBy(userMenuLinks, 'order'); + } } diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index d86d4812af5e3..6f5a2a031a7b2 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -97,7 +97,12 @@ describe('Security Plugin', () => { data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, }) - ).toBeUndefined(); + ).toEqual({ + navControlService: { + getUserMenuLinks$: expect.any(Function), + addUserMenuLinks: expect.any(Function), + }, + }); }); it('starts Management Service if `management` plugin is available', () => { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 700653c4cecb8..f94772c43dd89 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -146,11 +146,13 @@ export class SecurityPlugin public start(core: CoreStart, { management, securityOss }: PluginStartDependencies) { this.sessionTimeout.start(); - this.navControlService.start({ core }); this.securityCheckupService.start({ securityOssStart: securityOss, docLinks: core.docLinks }); + if (management) { this.managementService.start({ capabilities: core.application.capabilities }); } + + return { navControlService: this.navControlService.start({ core }) }; } public stop() { From 8ff92f2b4051faa4969a909c858ca6ae0fdace52 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 10 Nov 2020 10:30:45 -0600 Subject: [PATCH 57/87] [Workplace Search] Consolidate groups routes (#83015) * [Workplace Search] Consolidate groups routes This PR consolidates all of the groups route resgistration functions into a single export so that the `registerWorkplaceSearchRoutes` function only has to call the top-level routes * Remove redundant test --- .../server/routes/workplace_search/groups.ts | 10 ++++++++++ .../server/routes/workplace_search/index.ts | 18 ++---------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts index 35c585eb9f781..fa01f983bbb89 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/groups.ts @@ -223,3 +223,13 @@ export function registerBoostsGroupRoute({ } ); } + +export const registerGroupsRoutes = (dependencies: IRouteDependencies) => { + registerGroupsRoute(dependencies); + registerSearchGroupsRoute(dependencies); + registerGroupRoute(dependencies); + registerGroupUsersRoute(dependencies); + registerShareGroupRoute(dependencies); + registerAssignGroupRoute(dependencies); + registerBoostsGroupRoute(dependencies); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts index a5ebcc0d05298..0edf0b980cf21 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts @@ -7,23 +7,9 @@ import { IRouteDependencies } from '../../plugin'; import { registerOverviewRoute } from './overview'; -import { - registerGroupsRoute, - registerSearchGroupsRoute, - registerGroupRoute, - registerGroupUsersRoute, - registerShareGroupRoute, - registerAssignGroupRoute, - registerBoostsGroupRoute, -} from './groups'; +import { registerGroupsRoutes } from './groups'; export const registerWorkplaceSearchRoutes = (dependencies: IRouteDependencies) => { registerOverviewRoute(dependencies); - registerGroupsRoute(dependencies); - registerSearchGroupsRoute(dependencies); - registerGroupRoute(dependencies); - registerGroupUsersRoute(dependencies); - registerShareGroupRoute(dependencies); - registerAssignGroupRoute(dependencies); - registerBoostsGroupRoute(dependencies); + registerGroupsRoutes(dependencies); }; From 915f718c6ec226421c7ada18f667d7fa721d4271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Tue, 10 Nov 2020 17:36:35 +0100 Subject: [PATCH 58/87] [Security Solution] Fix DNS Network table query (#82778) --- .../components/paginated_table/helpers.ts | 5 +-- .../network/containers/network_dns/index.tsx | 4 +-- .../factory/network/dns/__mocks__/index.ts | 34 +++++++++++++++--- .../factory/network/dns/index.ts | 5 ++- .../network/dns/query.dns_network.dsl.ts | 36 +++++++++++-------- .../apis/security_solution/network_dns.ts | 5 ++- 6 files changed, 60 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/helpers.ts b/x-pack/plugins/security_solution/public/common/components/paginated_table/helpers.ts index 8fde81adc922a..9685a260d2a1a 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/helpers.ts @@ -8,13 +8,14 @@ import { PaginationInputPaginated } from '../../../graphql/types'; export const generateTablePaginationOptions = ( activePage: number, - limit: number + limit: number, + isBucketSort?: boolean ): PaginationInputPaginated => { const cursorStart = activePage * limit; return { activePage, cursorStart, fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, - querySize: limit + cursorStart, + querySize: isBucketSort ? limit : limit + cursorStart, }; }; diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index fc00f8866ed2e..108bfa0c9df69 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -77,7 +77,7 @@ export const useNetworkDns = ({ factoryQueryType: NetworkQueries.dns, filterQuery: createFilter(filterQuery), isPtrIncluded, - pagination: generateTablePaginationOptions(activePage, limit), + pagination: generateTablePaginationOptions(activePage, limit, true), sort, timerange: { interval: '12h', @@ -193,7 +193,7 @@ export const useNetworkDns = ({ isPtrIncluded, factoryQueryType: NetworkQueries.dns, filterQuery: createFilter(filterQuery), - pagination: generateTablePaginationOptions(activePage, limit), + pagination: generateTablePaginationOptions(activePage, limit, true), sort, timerange: { interval: '12h', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts index d3625a96c6db9..d4cef30f3b320 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/__mocks__/index.ts @@ -146,10 +146,23 @@ export const formattedSearchStrategyResponse = { dns_name_query_count: { terms: { field: 'dns.question.registered_domain', - size: 10, - order: { unique_domains: 'desc' }, + size: 1000000, }, aggs: { + bucket_sort: { + bucket_sort: { + sort: [ + { + unique_domains: { + order: 'desc', + }, + }, + { _key: { order: 'asc' } }, + ], + from: 0, + size: 10, + }, + }, unique_domains: { cardinality: { field: 'dns.question.name' } }, dns_bytes_in: { sum: { field: 'source.bytes' } }, dns_bytes_out: { sum: { field: 'destination.bytes' } }, @@ -204,10 +217,23 @@ export const expectedDsl = { dns_name_query_count: { terms: { field: 'dns.question.registered_domain', - size: 10, - order: { unique_domains: 'desc' }, + size: 1000000, }, aggs: { + bucket_sort: { + bucket_sort: { + sort: [ + { + unique_domains: { + order: 'desc', + }, + }, + { _key: { order: 'asc' } }, + ], + from: 0, + size: 10, + }, + }, unique_domains: { cardinality: { field: 'dns.question.name' } }, dns_bytes_in: { sum: { field: 'source.bytes' } }, dns_bytes_out: { sum: { field: 'destination.bytes' } }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts index ca7743126df4c..758731b674544 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/index.ts @@ -33,11 +33,10 @@ export const networkDns: SecuritySolutionFactory = { options: NetworkDnsRequestOptions, response: IEsSearchResponse ): Promise => { - const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const { activePage, fakePossibleCount } = options.pagination; const totalCount = getOr(0, 'aggregations.dns_count.value', response.rawResponse); - const networkDnsEdges: NetworkDnsEdges[] = getDnsEdges(response); + const edges: NetworkDnsEdges[] = getDnsEdges(response); const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; - const edges = networkDnsEdges.splice(cursorStart, querySize - cursorStart); const inspect = { dsl: [inspectStringifyObject(buildDnsQuery(options))], }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts index 85b9051189bfe..7043b15ebb4dd 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/dns/query.dns_network.dsl.ts @@ -15,25 +15,27 @@ import { } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../utils/build_query'; +const HUGE_QUERY_SIZE = 1000000; + type QueryOrder = - | { _count: Direction } - | { _key: Direction } - | { unique_domains: Direction } - | { dns_bytes_in: Direction } - | { dns_bytes_out: Direction }; + | { _count: { order: Direction } } + | { _key: { order: Direction } } + | { unique_domains: { order: Direction } } + | { dns_bytes_in: { order: Direction } } + | { dns_bytes_out: { order: Direction } }; const getQueryOrder = (sort: SortField): QueryOrder => { switch (sort.field) { case NetworkDnsFields.queryCount: - return { _count: sort.direction }; + return { _count: { order: sort.direction } }; case NetworkDnsFields.dnsName: - return { _key: sort.direction }; + return { _key: { order: sort.direction } }; case NetworkDnsFields.uniqueDomains: - return { unique_domains: sort.direction }; + return { unique_domains: { order: sort.direction } }; case NetworkDnsFields.dnsBytesIn: - return { dns_bytes_in: sort.direction }; + return { dns_bytes_in: { order: sort.direction } }; case NetworkDnsFields.dnsBytesOut: - return { dns_bytes_out: sort.direction }; + return { dns_bytes_out: { order: sort.direction } }; } assertUnreachable(sort.field); }; @@ -67,7 +69,7 @@ export const buildDnsQuery = ({ filterQuery, isPtrIncluded, sort, - pagination: { querySize }, + pagination: { cursorStart, querySize }, stackByField = 'dns.question.registered_domain', timerange: { from, to }, }: NetworkDnsRequestOptions) => { @@ -95,12 +97,16 @@ export const buildDnsQuery = ({ dns_name_query_count: { terms: { field: stackByField, - size: querySize, - order: { - ...getQueryOrder(sort), - }, + size: HUGE_QUERY_SIZE, }, aggs: { + bucket_sort: { + bucket_sort: { + sort: [getQueryOrder(sort), { _key: { order: 'asc' } }], + from: cursorStart, + size: querySize, + }, + }, unique_domains: { cardinality: { field: 'dns.question.name', diff --git a/x-pack/test/api_integration/apis/security_solution/network_dns.ts b/x-pack/test/api_integration/apis/security_solution/network_dns.ts index 966b8184965d1..9b7a39a279773 100644 --- a/x-pack/test/api_integration/apis/security_solution/network_dns.ts +++ b/x-pack/test/api_integration/apis/security_solution/network_dns.ts @@ -18,8 +18,7 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - // Failing: See https://github.com/elastic/kibana/issues/82207 - describe.skip('Network DNS', () => { + describe('Network DNS', () => { describe('With packetbeat', () => { before(() => esArchiver.load('packetbeat/dns')); after(() => esArchiver.unload('packetbeat/dns')); @@ -59,7 +58,7 @@ export default function ({ getService }: FtrProviderContext) { expect(networkDns.edges.length).to.be(10); expect(networkDns.totalCount).to.be(44); expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( - 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,crowbird.com,d1oxlq5h9kq8q5.cloudfront.net' + 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,d1oxlq5h9kq8q5.cloudfront.net,d3epxf4t8a32oh.cloudfront.net' ); expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); }); From 28305237a318212715491c4655f2b26e0f3d77bc Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Tue, 10 Nov 2020 10:50:54 -0600 Subject: [PATCH 59/87] Remove kibana-core-ui-designers (#82962) The people in `kibana-core-ui-designers` have been rolled into the `kibana-design` team. All `.scss` changes will now ping `kibana-design` since the `kibana-core-ui-designers` entries were overrides (occurring later in this file). --- .github/CODEOWNERS | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6da2d5d602186..78e17de4139bd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -369,13 +369,6 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib **/*.scss @elastic/kibana-design #CC# /packages/kbn-ui-framework/ @elastic/kibana-design -# Core UI design -/src/plugins/dashboard/**/*.scss @elastic/kibana-core-ui-designers -/src/plugins/embeddable/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/canvas/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/spaces/**/*.scss @elastic/kibana-core-ui-designers -/x-pack/plugins/security/**/*.scss @elastic/kibana-core-ui-designers - # Observability design /x-pack/plugins/apm/**/*.scss @elastic/observability-design /x-pack/plugins/infra/**/*.scss @elastic/observability-design From a63c390ae063db7ab9b4f8f1e1284b7b382d8b3b Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 10 Nov 2020 18:12:47 +0100 Subject: [PATCH 60/87] Remove redundant call to `_authenticate` API after access token is created. (#82980) --- .github/CODEOWNERS | 1 - .../server/authentication/providers/base.ts | 19 +++- .../authentication/providers/kerberos.test.ts | 70 +++---------- .../authentication/providers/kerberos.ts | 61 +++++------- .../authentication/providers/oidc.test.ts | 20 +--- .../server/authentication/providers/oidc.ts | 91 +++++++---------- .../authentication/providers/pki.test.ts | 88 ++++------------- .../server/authentication/providers/pki.ts | 40 +++----- .../authentication/providers/saml.test.ts | 33 +++---- .../server/authentication/providers/saml.ts | 91 +++++++---------- .../authentication/providers/token.test.ts | 99 ++----------------- .../server/authentication/providers/token.ts | 63 +++++------- .../server/authentication/tokens.test.ts | 8 +- .../security/server/authentication/tokens.ts | 13 ++- .../security/server/elasticsearch/index.ts | 3 + x-pack/scripts/functional_tests.js | 2 +- .../tests/pki/pki_auth.ts | 2 +- .../tests/token/header.ts} | 6 +- .../tests/token/index.ts} | 6 +- .../tests/token/login.ts} | 5 +- .../tests/token/logout.ts} | 5 +- .../tests/token/session.ts} | 19 ++-- .../token.config.ts} | 8 +- 23 files changed, 258 insertions(+), 495 deletions(-) rename x-pack/test/{token_api_integration/auth/header.js => security_api_integration/tests/token/header.ts} (91%) rename x-pack/test/{token_api_integration/auth/index.js => security_api_integration/tests/token/index.ts} (72%) rename x-pack/test/{token_api_integration/auth/login.js => security_api_integration/tests/token/login.ts} (94%) rename x-pack/test/{token_api_integration/auth/logout.js => security_api_integration/tests/token/logout.ts} (91%) rename x-pack/test/{token_api_integration/auth/session.js => security_api_integration/tests/token/session.ts} (91%) rename x-pack/test/{token_api_integration/config.js => security_api_integration/token.config.ts} (81%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 78e17de4139bd..150e6431ec968 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -257,7 +257,6 @@ /x-pack/test/security_api_integration/ @elastic/kibana-security /x-pack/test/security_functional/ @elastic/kibana-security /x-pack/test/spaces_api_integration/ @elastic/kibana-security -/x-pack/test/token_api_integration/ @elastic/kibana-security #CC# /src/legacy/ui/public/capabilities @elastic/kibana-security #CC# /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security #CC# /x-pack/plugins/security_solution/ @elastic/kibana-security diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index 030b2a6e968af..a5a68f2a49315 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -13,7 +13,8 @@ import { ILegacyClusterClient, Headers, } from '../../../../../../src/core/server'; -import { AuthenticatedUser } from '../../../common/model'; +import type { AuthenticatedUser } from '../../../common/model'; +import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { Tokens } from '../tokens'; @@ -109,10 +110,20 @@ export abstract class BaseAuthenticationProvider { * @param [authHeaders] Optional `Headers` dictionary to send with the request. */ protected async getUser(request: KibanaRequest, authHeaders: Headers = {}) { - return deepFreeze({ - ...(await this.options.client + return this.authenticationInfoToAuthenticatedUser( + await this.options.client .asScoped({ headers: { ...request.headers, ...authHeaders } }) - .callAsCurrentUser('shield.authenticate')), + .callAsCurrentUser('shield.authenticate') + ); + } + + /** + * Converts Elasticsearch Authentication result to a Kibana authenticated user. + * @param authenticationInfo Result returned from the `_authenticate` operation. + */ + protected authenticationInfoToAuthenticatedUser(authenticationInfo: AuthenticationInfo) { + return deepFreeze({ + ...authenticationInfo, authentication_provider: { type: this.type, name: this.options.name }, } as AuthenticatedUser); } diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index af26d1e60414a..eb4ac8f4dcbed 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -118,12 +118,10 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'some-token', refresh_token: 'some-refresh-token', + authentication: user, }); await expect(operation(request)).resolves.toEqual( @@ -136,10 +134,7 @@ describe('KerberosAuthenticationProvider', () => { ) ); - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer some-token' }, - }); - + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -153,13 +148,11 @@ describe('KerberosAuthenticationProvider', () => { headers: { authorization: 'negotiate spnego' }, }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'some-token', refresh_token: 'some-refresh-token', kerberos_authentication_response_token: 'response-token', + authentication: user, }); await expect(operation(request)).resolves.toEqual( @@ -173,10 +166,7 @@ describe('KerberosAuthenticationProvider', () => { ) ); - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer some-token' }, - }); - + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, }); @@ -250,33 +240,6 @@ describe('KerberosAuthenticationProvider', () => { expect(request.headers.authorization).toBe('negotiate spnego'); }); - - it('fails if could not retrieve user using the new access token.', async () => { - const request = httpServerMock.createKibanaRequest({ - headers: { authorization: 'negotiate spnego' }, - }); - - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: 'some-token', - refresh_token: 'some-refresh-token', - }); - - await expect(operation(request)).resolves.toEqual(AuthenticationResult.failed(failureReason)); - - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer some-token' }, - }); - - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { - body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' }, - }); - - expect(request.headers.authorization).toBe('negotiate spnego'); - }); } describe('`login` method', () => { @@ -381,32 +344,21 @@ describe('KerberosAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - it('succeeds with valid session even if requiring a token refresh', async () => { + it('succeeds with a valid session even if requiring a token refresh', async () => { const user = mockAuthenticatedUser(); const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { - if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ); - return mockScopedClusterClient; - } - - if (scopeableRequest?.headers.authorization === 'Bearer newfoo') { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - return mockScopedClusterClient; - } - - throw new Error('Unexpected call'); - }); + const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); + mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.tokens.refresh.mockResolvedValue({ accessToken: 'newfoo', refreshToken: 'newbar', + authenticationInfo: user, }); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index fa578b9dca45f..2e15893d0845f 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -10,11 +10,12 @@ import { LegacyElasticsearchErrorHelpers, KibanaRequest, } from '../../../../../../src/core/server'; +import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; import { canRedirectRequest } from '../can_redirect_request'; -import { Tokens, TokenPair } from '../tokens'; +import { Tokens, TokenPair, RefreshTokenResult } from '../tokens'; import { BaseAuthenticationProvider } from './base'; /** @@ -149,6 +150,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { access_token: string; refresh_token: string; kerberos_authentication_response_token?: string; + authentication: AuthenticationInfo; }; try { tokens = await this.options.client.callAsInternalUser('shield.getAccessToken', { @@ -203,23 +205,16 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { }; } - try { - // Then attempt to query for the user details using the new token - const authHeaders = { - authorization: new HTTPAuthorizationHeader('Bearer', tokens.access_token).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('User has been authenticated with new access token'); - return AuthenticationResult.succeeded(user, { - authHeaders, + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(tokens.authentication), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', tokens.access_token).toString(), + }, authResponseHeaders, state: { accessToken: tokens.access_token, refreshToken: tokens.refresh_token }, - }); - } catch (err) { - this.logger.debug(`Failed to authenticate request via access token: ${err.message}`); - return AuthenticationResult.failed(err); - } + } + ); } /** @@ -260,9 +255,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { private async authenticateViaRefreshToken(request: KibanaRequest, state: ProviderState) { this.logger.debug('Trying to refresh access token.'); - let refreshedTokenPair: TokenPair | null; + let refreshTokenResult: RefreshTokenResult | null; try { - refreshedTokenPair = await this.options.tokens.refresh(state.refreshToken); + refreshTokenResult = await this.options.tokens.refresh(state.refreshToken); } catch (err) { return AuthenticationResult.failed(err); } @@ -270,28 +265,22 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { // If refresh token is no longer valid, let's try to renegotiate new tokens using SPNEGO. We // allow this because expired underlying token is an implementation detail and Kibana user // facing session is still valid. - if (refreshedTokenPair === null) { + if (refreshTokenResult === null) { this.logger.debug('Both access and refresh tokens are expired. Re-authenticating...'); return this.authenticateViaSPNEGO(request, state); } - try { - const authHeaders = { - authorization: new HTTPAuthorizationHeader( - 'Bearer', - refreshedTokenPair.accessToken - ).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('Request has been authenticated via refreshed token.'); - return AuthenticationResult.succeeded(user, { authHeaders, state: refreshedTokenPair }); - } catch (err) { - this.logger.debug( - `Failed to authenticate user using newly refreshed access token: ${err.message}` - ); - return AuthenticationResult.failed(err); - } + this.logger.debug('Request has been authenticated via refreshed token.'); + const { accessToken, refreshToken, authenticationInfo } = refreshTokenResult; + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(authenticationInfo), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + }, + state: { accessToken, refreshToken }, + } + ); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index dfea7e508b333..126306c885e53 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -175,6 +175,7 @@ describe('OIDCAuthenticationProvider', () => { const { request, attempt, expectedRedirectURI } = getMocks(); mockOptions.client.callAsInternalUser.mockResolvedValue({ + authentication: mockUser, access_token: 'some-token', refresh_token: 'some-refresh-token', }); @@ -440,25 +441,14 @@ describe('OIDCAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'expired-token', refreshToken: 'valid-refresh-token' }; - mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { - if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { - const mockScopedClusterClientToFail = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClientToFail.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ); - return mockScopedClusterClientToFail; - } - - if (scopeableRequest?.headers.authorization === 'Bearer new-access-token') { - return mockScopedClusterClient; - } - - throw new Error('Unexpected call'); - }); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); mockOptions.tokens.refresh.mockResolvedValue({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', + authenticationInfo: mockUser, }); await expect( diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index eede9f334a5a1..250641d1cf174 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -7,12 +7,12 @@ import Boom from '@hapi/boom'; import type from 'type-detect'; import { KibanaRequest } from '../../../../../../src/core/server'; -import { AuthenticatedUser } from '../../../common/model'; +import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; -import { Tokens, TokenPair } from '../tokens'; +import { Tokens, TokenPair, RefreshTokenResult } from '../tokens'; import { AuthenticationProviderOptions, BaseAuthenticationProvider, @@ -243,46 +243,31 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { } // We have all the necessary parameters, so attempt to complete the OpenID Connect Authentication - let accessToken; - let refreshToken; + let result: { access_token: string; refresh_token: string; authentication: AuthenticationInfo }; try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/oidc/authenticate`. - const authenticateResponse = await this.options.client.callAsInternalUser( - 'shield.oidcAuthenticate', - { - body: { - state: stateOIDCState, - nonce: stateNonce, - redirect_uri: authenticationResponseURI, - realm: this.realm, - }, - } - ); - - accessToken = authenticateResponse.access_token; - refreshToken = authenticateResponse.refresh_token; - } catch (err) { - this.logger.debug(`Failed to authenticate request via OpenID Connect: ${err.message}`); - return AuthenticationResult.failed(err); - } - - // Now we need to retrieve full user information. - let user: Readonly; - try { - user = await this.getUser(request, { - authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + result = await this.options.client.callAsInternalUser('shield.oidcAuthenticate', { + body: { + state: stateOIDCState, + nonce: stateNonce, + redirect_uri: authenticationResponseURI, + realm: this.realm, + }, }); } catch (err) { - this.logger.debug(`Failed to retrieve user using access token: ${err.message}`); + this.logger.debug(`Failed to authenticate request via OpenID Connect: ${err.message}`); return AuthenticationResult.failed(err); } this.logger.debug('Login has been performed with OpenID Connect response.'); - return AuthenticationResult.redirectTo(stateRedirectURL, { - state: { accessToken, refreshToken, realm: this.realm }, - user, + state: { + accessToken: result.access_token, + refreshToken: result.refresh_token, + realm: this.realm, + }, + user: this.authenticationInfoToAuthenticatedUser(result.authentication), }); } @@ -356,20 +341,17 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. * @param state State value previously stored by the provider. */ - private async authenticateViaRefreshToken( - request: KibanaRequest, - { refreshToken }: ProviderState - ) { + private async authenticateViaRefreshToken(request: KibanaRequest, state: ProviderState) { this.logger.debug('Trying to refresh elasticsearch access token.'); - if (!refreshToken) { + if (!state.refreshToken) { this.logger.debug('Refresh token is not found in state.'); return AuthenticationResult.notHandled(); } - let refreshedTokenPair: TokenPair | null; + let refreshTokenResult: RefreshTokenResult | null; try { - refreshedTokenPair = await this.options.tokens.refresh(refreshToken); + refreshTokenResult = await this.options.tokens.refresh(state.refreshToken); } catch (err) { return AuthenticationResult.failed(err); } @@ -380,7 +362,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { // message. There are two reasons for `400` and not `401`: Elasticsearch search responds with `400` so it // seems logical to do the same on Kibana side and `401` would force user to logout and do full SLO if it's // supported. - if (refreshedTokenPair === null) { + if (refreshTokenResult === null) { if (canStartNewSession(request)) { this.logger.debug( 'Both elasticsearch access and refresh tokens are expired. Re-initiating OpenID Connect authentication.' @@ -393,24 +375,17 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { ); } - try { - const authHeaders = { - authorization: new HTTPAuthorizationHeader( - 'Bearer', - refreshedTokenPair.accessToken - ).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('Request has been authenticated via refreshed token.'); - return AuthenticationResult.succeeded(user, { - authHeaders, - state: { ...refreshedTokenPair, realm: this.realm }, - }); - } catch (err) { - this.logger.debug(`Failed to refresh elasticsearch access token: ${err.message}`); - return AuthenticationResult.failed(err); - } + this.logger.debug('Request has been authenticated via refreshed token.'); + const { accessToken, refreshToken, authenticationInfo } = refreshTokenResult; + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(authenticationInfo), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + }, + state: { accessToken, refreshToken, realm: this.realm }, + } + ); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 94308ab5f2403..aa85b8a43af4d 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -120,10 +120,10 @@ describe('PKIAuthenticationProvider', () => { }), }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + mockOptions.client.callAsInternalUser.mockResolvedValue({ + authentication: user, + access_token: 'access-token', + }); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -144,10 +144,7 @@ describe('PKIAuthenticationProvider', () => { ], }, }); - - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer access-token' }, - }); + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); expect(request.headers).not.toHaveProperty('authorization'); }); @@ -162,10 +159,10 @@ describe('PKIAuthenticationProvider', () => { }), }); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + mockOptions.client.callAsInternalUser.mockResolvedValue({ + authentication: user, + access_token: 'access-token', + }); await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( @@ -181,10 +178,7 @@ describe('PKIAuthenticationProvider', () => { expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, }); - - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer access-token' }, - }); + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); expect(request.headers).not.toHaveProperty('authorization'); }); @@ -209,35 +203,6 @@ describe('PKIAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - - it('fails if could not retrieve user using the new access token.', async () => { - const request = httpServerMock.createKibanaRequest({ - headers: {}, - socket: getMockSocket({ - authorized: true, - peerCertificate: getMockPeerCertificate('2A:7A:C2:DD'), - }), - }); - - const failureReason = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); - - await expect(operation(request)).resolves.toEqual(AuthenticationResult.failed(failureReason)); - - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { - body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, - }); - - expectAuthenticateCall(mockOptions.client, { - headers: { authorization: 'Bearer access-token' }, - }); - - expect(request.headers).not.toHaveProperty('authorization'); - }); } describe('`login` method', () => { @@ -365,10 +330,10 @@ describe('PKIAuthenticationProvider', () => { }); const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '3A:9A:C5:DD' }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + mockOptions.client.callAsInternalUser.mockResolvedValue({ + authentication: user, + access_token: 'access-token', + }); await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( @@ -402,25 +367,14 @@ describe('PKIAuthenticationProvider', () => { const user = mockAuthenticatedUser({ authentication_provider: { type: 'pki', name: 'pki' } }); const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser - // In response to call with an expired token. - .mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ) - // In response to a call with a new token. - .mockResolvedValueOnce(user) // In response to call with an expired token. - .mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ) - // In response to a call with a new token. - .mockResolvedValueOnce(user) // In response to call with an expired token. - .mockRejectedValueOnce( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ) - // In response to a call with a new token. - .mockResolvedValueOnce(user); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + mockOptions.client.callAsInternalUser.mockResolvedValue({ + authentication: user, + access_token: 'access-token', + }); const nonAjaxRequest = httpServerMock.createKibanaRequest({ socket: getMockSocket({ diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 3629a0ac34f02..974a838127e1d 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { DetailedPeerCertificate } from 'tls'; import { KibanaRequest } from '../../../../../../src/core/server'; +import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { HTTPAuthorizationHeader } from '../http_authentication'; @@ -218,13 +219,11 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { // We should collect entire certificate chain as an ordered array of certificates encoded as base64 strings. const certificateChain = this.getCertificateChain(peerCertificate); - let accessToken: string; + let result: { access_token: string; authentication: AuthenticationInfo }; try { - accessToken = ( - await this.options.client.callAsInternalUser('shield.delegatePKI', { - body: { x509_certificate_chain: certificateChain }, - }) - ).access_token; + result = await this.options.client.callAsInternalUser('shield.delegatePKI', { + body: { x509_certificate_chain: certificateChain }, + }); } catch (err) { this.logger.debug( `Failed to exchange peer certificate chain to an access token: ${err.message}` @@ -233,27 +232,18 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { } this.logger.debug('Successfully retrieved access token in exchange to peer certificate chain.'); - - try { - // Then attempt to query for the user details using the new token - const authHeaders = { - authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('User has been authenticated with new access token'); - return AuthenticationResult.succeeded(user, { - authHeaders, + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(result.authentication), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', result.access_token).toString(), + }, state: { - accessToken, - // NodeJS typings don't include `fingerprint256` yet. - peerCertificateFingerprint256: (peerCertificate as any).fingerprint256, + accessToken: result.access_token, + peerCertificateFingerprint256: peerCertificate.fingerprint256, }, - }); - } catch (err) { - this.logger.debug(`Failed to authenticate request via access token: ${err.message}`); - return AuthenticationResult.failed(err); - } + } + ); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index a1f2e99c13357..03c0b7404da39 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -64,6 +64,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'some-token', refresh_token: 'some-refresh-token', + authentication: mockUser, }); await expect( @@ -99,6 +100,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'some-token', refresh_token: 'some-refresh-token', + authentication: mockUser, }); provider = new SAMLAuthenticationProvider(mockOptions, { @@ -180,6 +182,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'user-initiated-login-token', refresh_token: 'user-initiated-login-refresh-token', + authentication: mockUser, }); await expect( @@ -211,6 +214,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'user-initiated-login-token', refresh_token: 'user-initiated-login-refresh-token', + authentication: mockUser, }); provider = new SAMLAuthenticationProvider(mockOptions, { @@ -250,6 +254,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'idp-initiated-login-token', refresh_token: 'idp-initiated-login-refresh-token', + authentication: mockUser, }); await expect( @@ -306,6 +311,7 @@ describe('SAMLAuthenticationProvider', () => { username: 'user', access_token: 'valid-token', refresh_token: 'valid-refresh-token', + authentication: mockUser, }); provider = new SAMLAuthenticationProvider(mockOptions, { @@ -459,6 +465,7 @@ describe('SAMLAuthenticationProvider', () => { username: 'user', access_token: 'new-valid-token', refresh_token: 'new-valid-refresh-token', + authentication: mockUser, }); const failureReason = new Error('Failed to invalidate token!'); @@ -519,6 +526,7 @@ describe('SAMLAuthenticationProvider', () => { username: 'user', access_token: 'new-valid-token', refresh_token: 'new-valid-refresh-token', + authentication: mockUser, }); mockOptions.tokens.invalidate.mockResolvedValue(undefined); @@ -566,15 +574,11 @@ describe('SAMLAuthenticationProvider', () => { // The first call is made using tokens from existing session. mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(() => response); - // The second call is made using new tokens. - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(() => - Promise.resolve(mockUser) - ); - mockOptions.client.callAsInternalUser.mockResolvedValue({ username: 'user', access_token: 'new-valid-token', refresh_token: 'new-valid-refresh-token', + authentication: mockUser, }); mockOptions.tokens.invalidate.mockResolvedValue(undefined); @@ -849,25 +853,14 @@ describe('SAMLAuthenticationProvider', () => { realm: 'test-realm', }; - mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { - if (scopeableRequest?.headers.authorization === `Bearer ${state.accessToken}`) { - const mockScopedClusterClientToFail = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClientToFail.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ); - return mockScopedClusterClientToFail; - } - - if (scopeableRequest?.headers.authorization === 'Bearer new-access-token') { - return mockScopedClusterClient; - } - - throw new Error('Unexpected call'); - }); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); mockOptions.tokens.refresh.mockResolvedValue({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', + authenticationInfo: mockUser, }); await expect(provider.authenticate(request, state)).resolves.toEqual( diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 54619c851470a..8f31968e5f639 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -6,13 +6,13 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; -import { AuthenticatedUser } from '../../../common/model'; import { isInternalURL } from '../../../common/is_internal_url'; +import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { HTTPAuthorizationHeader } from '../http_authentication'; -import { Tokens, TokenPair } from '../tokens'; +import { Tokens, TokenPair, RefreshTokenResult } from '../tokens'; import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base'; /** @@ -338,24 +338,17 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { : 'Login has been initiated by Identity Provider.' ); - let accessToken; - let refreshToken; + let result: { access_token: string; refresh_token: string; authentication: AuthenticationInfo }; try { // This operation should be performed on behalf of the user with a privilege that normal // user usually doesn't have `cluster:admin/xpack/security/saml/authenticate`. - const authenticateResponse = await this.options.client.callAsInternalUser( - 'shield.samlAuthenticate', - { - body: { - ids: !isIdPInitiatedLogin ? [stateRequestId] : [], - content: samlResponse, - realm: this.realm, - }, - } - ); - - accessToken = authenticateResponse.access_token; - refreshToken = authenticateResponse.refresh_token; + result = await this.options.client.callAsInternalUser('shield.samlAuthenticate', { + body: { + ids: !isIdPInitiatedLogin ? [stateRequestId] : [], + content: samlResponse, + realm: this.realm, + }, + }); } catch (err) { this.logger.debug(`Failed to log in with SAML response: ${err.message}`); @@ -367,17 +360,6 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { : AuthenticationResult.failed(err); } - // Now we need to retrieve full user information. - let user: Readonly; - try { - user = await this.getUser(request, { - authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), - }); - } catch (err) { - this.logger.debug(`Failed to retrieve user using access token: ${err.message}`); - return AuthenticationResult.failed(err); - } - // IdP can pass `RelayState` with the deep link in Kibana during IdP initiated login and // depending on the configuration we may need to redirect user to this URL. let redirectURLFromRelayState; @@ -401,7 +383,14 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Login has been performed with SAML response.'); return AuthenticationResult.redirectTo( redirectURLFromRelayState || stateRedirectURL || `${this.options.basePath.get(request)}/`, - { state: { accessToken, refreshToken, realm: this.realm }, user } + { + state: { + accessToken: result.access_token, + refreshToken: result.refresh_token, + realm: this.realm, + }, + user: this.authenticationInfoToAuthenticatedUser(result.authentication), + } ); } @@ -494,20 +483,17 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. * @param state State value previously stored by the provider. */ - private async authenticateViaRefreshToken( - request: KibanaRequest, - { refreshToken }: ProviderState - ) { + private async authenticateViaRefreshToken(request: KibanaRequest, state: ProviderState) { this.logger.debug('Trying to refresh access token.'); - if (!refreshToken) { + if (!state.refreshToken) { this.logger.debug('Refresh token is not found in state.'); return AuthenticationResult.notHandled(); } - let refreshedTokenPair: TokenPair | null; + let refreshTokenResult: RefreshTokenResult | null; try { - refreshedTokenPair = await this.options.tokens.refresh(refreshToken); + refreshTokenResult = await this.options.tokens.refresh(state.refreshToken); } catch (err) { return AuthenticationResult.failed(err); } @@ -517,7 +503,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { // handshake. Obviously we can't do that for AJAX requests, so we just reply with `400` and clear error message. // There are two reasons for `400` and not `401`: Elasticsearch search responds with `400` so it seems logical // to do the same on Kibana side and `401` would force user to logout and do full SLO if it's supported. - if (refreshedTokenPair === null) { + if (refreshTokenResult === null) { if (canStartNewSession(request)) { this.logger.debug( 'Both access and refresh tokens are expired. Capturing redirect URL and re-initiating SAML handshake.' @@ -530,26 +516,17 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { ); } - try { - const authHeaders = { - authorization: new HTTPAuthorizationHeader( - 'Bearer', - refreshedTokenPair.accessToken - ).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('Request has been authenticated via refreshed token.'); - return AuthenticationResult.succeeded(user, { - authHeaders, - state: { realm: this.realm, ...refreshedTokenPair }, - }); - } catch (err) { - this.logger.debug( - `Failed to authenticate user using newly refreshed access token: ${err.message}` - ); - return AuthenticationResult.failed(err); - } + this.logger.debug('Request has been authenticated via refreshed token.'); + const { accessToken, refreshToken, authenticationInfo } = refreshTokenResult; + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(authenticationInfo), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + }, + state: { accessToken, refreshToken, realm: this.realm }, + } + ); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 4501004ab69c1..e09400e9bb44a 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -49,13 +49,10 @@ describe('TokenAuthenticationProvider', () => { const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; const authorization = `Bearer ${tokenPair.accessToken}`; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: tokenPair.accessToken, refresh_token: tokenPair.refreshToken, + authentication: user, }); await expect(provider.login(request, credentials)).resolves.toEqual( @@ -65,8 +62,7 @@ describe('TokenAuthenticationProvider', () => { ) ); - expectAuthenticateCall(mockOptions.client, { headers: { authorization } }); - + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { body: { grant_type: 'password', ...credentials }, @@ -93,36 +89,6 @@ describe('TokenAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - - it('fails if user cannot be retrieved during login attempt', async () => { - const request = httpServerMock.createKibanaRequest({ headers: {} }); - const credentials = { username: 'user', password: 'password' }; - const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - const authorization = `Bearer ${tokenPair.accessToken}`; - - mockOptions.client.callAsInternalUser.mockResolvedValue({ - access_token: tokenPair.accessToken, - refresh_token: tokenPair.refreshToken, - }); - - const authenticationError = new Error('Some error'); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); - mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); - - await expect(provider.login(request, credentials)).resolves.toEqual( - AuthenticationResult.failed(authenticationError) - ); - - expectAuthenticateCall(mockOptions.client, { headers: { authorization } }); - - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { - body: { grant_type: 'password', ...credentials }, - }); - - expect(request.headers).not.toHaveProperty('authorization'); - }); }); describe('`authenticate` method', () => { @@ -211,27 +177,16 @@ describe('TokenAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { - if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ); - return mockScopedClusterClient; - } - - if (scopeableRequest?.headers.authorization === 'Bearer newfoo') { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); - return mockScopedClusterClient; - } - - throw new Error('Unexpected call'); - }); + const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); + mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient); mockOptions.tokens.refresh.mockResolvedValue({ accessToken: 'newfoo', refreshToken: 'newbar', + authenticationInfo: user, }); await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( @@ -381,44 +336,6 @@ describe('TokenAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - - it('fails if new access token is rejected after successful refresh', async () => { - const request = httpServerMock.createKibanaRequest(); - const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - - const authenticationError = new errors.AuthenticationException('Some error'); - mockOptions.client.asScoped.mockImplementation((scopeableRequest) => { - if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( - LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) - ); - return mockScopedClusterClient; - } - - if (scopeableRequest?.headers.authorization === 'Bearer newfoo') { - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError); - return mockScopedClusterClient; - } - - throw new Error('Unexpected call'); - }); - - mockOptions.tokens.refresh.mockResolvedValue({ - accessToken: 'newfoo', - refreshToken: 'newbar', - }); - - await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( - AuthenticationResult.failed(authenticationError) - ); - - expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); - - expect(request.headers).not.toHaveProperty('authorization'); - }); }); describe('`logout` method', () => { diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index f919c20c15225..2032db4b0a8f2 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -10,7 +10,7 @@ import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { HTTPAuthorizationHeader } from '../http_authentication'; -import { Tokens, TokenPair } from '../tokens'; +import { Tokens, TokenPair, RefreshTokenResult } from '../tokens'; import { BaseAuthenticationProvider } from './base'; /** @@ -63,23 +63,21 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { const { access_token: accessToken, refresh_token: refreshToken, + authentication: authenticationInfo, } = await this.options.client.callAsInternalUser('shield.getAccessToken', { body: { grant_type: 'password', username, password }, }); this.logger.debug('Get token API request to Elasticsearch successful'); - - // Then attempt to query for the user details using the new token - const authHeaders = { - authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('Login has been successfully performed.'); - return AuthenticationResult.succeeded(user, { - authHeaders, - state: { accessToken, refreshToken }, - }); + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(authenticationInfo), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + }, + state: { accessToken, refreshToken }, + } + ); } catch (err) { this.logger.debug(`Failed to perform a login: ${err.message}`); return AuthenticationResult.failed(err); @@ -191,22 +189,19 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. * @param state State value previously stored by the provider. */ - private async authenticateViaRefreshToken( - request: KibanaRequest, - { refreshToken }: ProviderState - ) { + private async authenticateViaRefreshToken(request: KibanaRequest, state: ProviderState) { this.logger.debug('Trying to refresh access token.'); - let refreshedTokenPair: TokenPair | null; + let refreshTokenResult: RefreshTokenResult | null; try { - refreshedTokenPair = await this.options.tokens.refresh(refreshToken); + refreshTokenResult = await this.options.tokens.refresh(state.refreshToken); } catch (err) { return AuthenticationResult.failed(err); } // If refresh token is no longer valid, then we should clear session and redirect user to the // login page to re-authenticate, or fail if redirect isn't possible. - if (refreshedTokenPair === null) { + if (refreshTokenResult === null) { if (canStartNewSession(request)) { this.logger.debug('Clearing session since both access and refresh tokens are expired.'); @@ -219,23 +214,17 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { ); } - try { - const authHeaders = { - authorization: new HTTPAuthorizationHeader( - 'Bearer', - refreshedTokenPair.accessToken - ).toString(), - }; - const user = await this.getUser(request, authHeaders); - - this.logger.debug('Request has been authenticated via refreshed token.'); - return AuthenticationResult.succeeded(user, { authHeaders, state: refreshedTokenPair }); - } catch (err) { - this.logger.debug( - `Failed to authenticate user using newly refreshed access token: ${err.message}` - ); - return AuthenticationResult.failed(err); - } + this.logger.debug('Request has been authenticated via refreshed token.'); + const { accessToken, refreshToken, authenticationInfo } = refreshTokenResult; + return AuthenticationResult.succeeded( + this.authenticationInfoToAuthenticatedUser(authenticationInfo), + { + authHeaders: { + authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), + }, + state: { accessToken, refreshToken }, + } + ); } /** diff --git a/x-pack/plugins/security/server/authentication/tokens.test.ts b/x-pack/plugins/security/server/authentication/tokens.test.ts index e8cf37330aff2..18fdcf8608d29 100644 --- a/x-pack/plugins/security/server/authentication/tokens.test.ts +++ b/x-pack/plugins/security/server/authentication/tokens.test.ts @@ -7,6 +7,7 @@ import { errors } from 'elasticsearch'; import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; +import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import { ILegacyClusterClient, @@ -78,13 +79,18 @@ describe('Tokens', () => { }); it('returns token pair if refresh API call succeeds', async () => { + const authenticationInfo = mockAuthenticatedUser(); const tokenPair = { accessToken: 'access-token', refreshToken: 'refresh-token' }; mockClusterClient.callAsInternalUser.mockResolvedValue({ access_token: tokenPair.accessToken, refresh_token: tokenPair.refreshToken, + authentication: authenticationInfo, }); - await expect(tokens.refresh(refreshToken)).resolves.toEqual(tokenPair); + await expect(tokens.refresh(refreshToken)).resolves.toEqual({ + authenticationInfo, + ...tokenPair, + }); expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', { diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index 3918b0b190a15..a435452ae112f 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -5,6 +5,7 @@ */ import { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; +import type { AuthenticationInfo } from '../elasticsearch'; import { getErrorStatusCode } from '../errors'; /** @@ -24,6 +25,13 @@ export interface TokenPair { readonly refreshToken: string; } +/** + * Represents the result of the token refresh operation. + */ +export interface RefreshTokenResult extends TokenPair { + authenticationInfo: AuthenticationInfo; +} + /** * Class responsible for managing access and refresh tokens (refresh, invalidate, etc.) used by * various authentication providers. @@ -44,19 +52,20 @@ export class Tokens { * Tries to exchange provided refresh token to a new pair of access and refresh tokens. * @param existingRefreshToken Refresh token to send to the refresh token API. */ - public async refresh(existingRefreshToken: string): Promise { + public async refresh(existingRefreshToken: string): Promise { try { // Token should be refreshed by the same user that obtained that token. const { access_token: accessToken, refresh_token: refreshToken, + authentication: authenticationInfo, } = await this.options.client.callAsInternalUser('shield.getAccessToken', { body: { grant_type: 'refresh_token', refresh_token: existingRefreshToken }, }); this.logger.debug('Access token has been successfully refreshed.'); - return { accessToken, refreshToken }; + return { accessToken, refreshToken, authenticationInfo }; } catch (err) { this.logger.debug(`Failed to refresh access token: ${err.message}`); diff --git a/x-pack/plugins/security/server/elasticsearch/index.ts b/x-pack/plugins/security/server/elasticsearch/index.ts index 793bdc1c6ad26..23e4876904c31 100644 --- a/x-pack/plugins/security/server/elasticsearch/index.ts +++ b/x-pack/plugins/security/server/elasticsearch/index.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AuthenticatedUser } from '../../common/model'; + +export type AuthenticationInfo = Omit; export { ElasticsearchService, ElasticsearchServiceSetup, diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 5e877717fd21e..9db102e487116 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -42,7 +42,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/security_api_integration/pki.config.ts'), require.resolve('../test/security_api_integration/oidc.config.ts'), require.resolve('../test/security_api_integration/oidc_implicit_flow.config.ts'), - require.resolve('../test/token_api_integration/config.js'), + require.resolve('../test/security_api_integration/token.config.ts'), require.resolve('../test/observability_api_integration/basic/config.ts'), require.resolve('../test/observability_api_integration/trial/config.ts'), require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'), diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 0331f756712ca..93eabe33dc687 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -179,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { authentication_realm: { name: 'pki1', type: 'pki' }, lookup_realm: { name: 'pki1', type: 'pki' }, authentication_provider: { name: 'pki', type: 'pki' }, - authentication_type: 'token', + authentication_type: 'realm', }); checkCookieIsSet(request.cookie(response.headers['set-cookie'][0])!); diff --git a/x-pack/test/token_api_integration/auth/header.js b/x-pack/test/security_api_integration/tests/token/header.ts similarity index 91% rename from x-pack/test/token_api_integration/auth/header.js rename to x-pack/test/security_api_integration/tests/token/header.ts index 0cc233b56d984..2150d7a6269b0 100644 --- a/x-pack/test/token_api_integration/auth/header.js +++ b/x-pack/test/security_api_integration/tests/token/header.ts @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function ({ getService }) { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('legacyEs'); async function createToken() { - const { access_token: accessToken } = await es.shield.getAccessToken({ + const { access_token: accessToken } = await (es as any).shield.getAccessToken({ body: { grant_type: 'password', username: 'elastic', diff --git a/x-pack/test/token_api_integration/auth/index.js b/x-pack/test/security_api_integration/tests/token/index.ts similarity index 72% rename from x-pack/test/token_api_integration/auth/index.js rename to x-pack/test/security_api_integration/tests/token/index.ts index e7b5a5b46a503..e9bf6c641fb1f 100644 --- a/x-pack/test/token_api_integration/auth/index.js +++ b/x-pack/test/security_api_integration/tests/token/index.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function ({ loadTestFile }) { - describe('token-based auth', function () { +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security APIs - Token', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./login')); loadTestFile(require.resolve('./logout')); diff --git a/x-pack/test/token_api_integration/auth/login.js b/x-pack/test/security_api_integration/tests/token/login.ts similarity index 94% rename from x-pack/test/token_api_integration/auth/login.js rename to x-pack/test/security_api_integration/tests/token/login.ts index b2dd870e018da..82abb10847e06 100644 --- a/x-pack/test/token_api_integration/auth/login.js +++ b/x-pack/test/security_api_integration/tests/token/login.ts @@ -5,11 +5,12 @@ */ import request from 'request'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); - function extractSessionCookie(response) { + function extractSessionCookie(response: { headers: Record }) { const cookie = (response.headers['set-cookie'] || []).find((header) => header.startsWith('sid=') ); diff --git a/x-pack/test/token_api_integration/auth/logout.js b/x-pack/test/security_api_integration/tests/token/logout.ts similarity index 91% rename from x-pack/test/token_api_integration/auth/logout.js rename to x-pack/test/security_api_integration/tests/token/logout.ts index fcc0e8182158f..ccd8b4586be2d 100644 --- a/x-pack/test/token_api_integration/auth/logout.js +++ b/x-pack/test/security_api_integration/tests/token/logout.ts @@ -5,11 +5,12 @@ */ import request from 'request'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); - function extractSessionCookie(response) { + function extractSessionCookie(response: { headers: Record }) { const cookie = (response.headers['set-cookie'] || []).find((header) => header.startsWith('sid=') ); diff --git a/x-pack/test/token_api_integration/auth/session.js b/x-pack/test/security_api_integration/tests/token/session.ts similarity index 91% rename from x-pack/test/token_api_integration/auth/session.js rename to x-pack/test/security_api_integration/tests/token/session.ts index 1f69b06315a80..30e004a0fff3c 100644 --- a/x-pack/test/token_api_integration/auth/session.js +++ b/x-pack/test/security_api_integration/tests/token/session.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import request from 'request'; +import request, { Cookie } from 'request'; import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -const delay = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms)); +const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); - function extractSessionCookie(response) { + function extractSessionCookie(response: { headers: Record }) { const cookie = (response.headers['set-cookie'] || []).find((header) => header.startsWith('sid=') ); @@ -68,7 +69,7 @@ export default function ({ getService }) { }); describe('API access with expired access token.', function () { - const expectNewSessionCookie = (originalCookie, newCookie) => { + const expectNewSessionCookie = (originalCookie: Cookie, newCookie: Cookie) => { if (!newCookie) { throw new Error('No session cookie set after token refresh'); } @@ -97,7 +98,7 @@ export default function ({ getService }) { .set('cookie', originalCookie.cookieString()) .expect(200); - const firstNewCookie = extractSessionCookie(firstResponse); + const firstNewCookie = extractSessionCookie(firstResponse)!; expectNewSessionCookie(originalCookie, firstNewCookie); // Request with old cookie should return another valid cookie we can use to authenticate requests @@ -108,7 +109,7 @@ export default function ({ getService }) { .set('Cookie', originalCookie.cookieString()) .expect(200); - const secondNewCookie = extractSessionCookie(secondResponse); + const secondNewCookie = extractSessionCookie(secondResponse)!; expectNewSessionCookie(originalCookie, secondNewCookie); if (secondNewCookie.value === firstNewCookie.value) { @@ -132,7 +133,7 @@ export default function ({ getService }) { }); describe('API access with missing access token document.', () => { - let sessionCookie; + let sessionCookie: Cookie; beforeEach(async () => (sessionCookie = await createSessionCookie())); it('should clear cookie and redirect to login', async function () { @@ -155,7 +156,7 @@ export default function ({ getService }) { const cookies = response.headers['set-cookie']; expect(cookies).to.have.length(1); - const cookie = request.cookie(cookies[0]); + const cookie = request.cookie(cookies[0])!; expect(cookie.key).to.be('sid'); expect(cookie.value).to.be.empty(); expect(cookie.path).to.be('/'); diff --git a/x-pack/test/token_api_integration/config.js b/x-pack/test/security_api_integration/token.config.ts similarity index 81% rename from x-pack/test/token_api_integration/config.js rename to x-pack/test/security_api_integration/token.config.ts index 3e78a98067a8f..c7afa51edba5e 100644 --- a/x-pack/test/token_api_integration/config.js +++ b/x-pack/test/security_api_integration/token.config.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -export default async function ({ readConfigFile }) { +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); return { - testFiles: [require.resolve('./auth')], + testFiles: [require.resolve('./tests/token')], servers: xPackAPITestsConfig.get('servers'), security: { disableTestUser: true }, services: { @@ -16,7 +18,7 @@ export default async function ({ readConfigFile }) { supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, junit: { - reportName: 'Token-auth API Integration Tests', + reportName: 'X-Pack Security API Integration Tests (Token)', }, esTestCluster: { From ece505b0759fcb5cdfb4acaf19e432e407dda9e2 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 10 Nov 2020 21:26:35 +0300 Subject: [PATCH 61/87] require schema for UiSettings (#83037) --- .../ui_settings/ui_settings_service.test.ts | 29 +++++++++++++++++++ .../server/ui_settings/ui_settings_service.ts | 6 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index 0c17a3a614d60..f4e24e3a517c3 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -89,6 +89,20 @@ describe('uiSettings', () => { describe('#start', () => { describe('validation', () => { + it('throws if validation schema is not provided', async () => { + const { register } = await service.setup(setupDeps); + register({ + // @ts-expect-error schema is required key + custom: { + value: 42, + }, + }); + + await expect(service.start()).rejects.toMatchInlineSnapshot( + `[Error: Validation schema is not provided for [custom] UI Setting]` + ); + }); + it('validates registered definitions', async () => { const { register } = await service.setup(setupDeps); register({ @@ -125,6 +139,21 @@ describe('uiSettings', () => { `[Error: [ui settings overrides [custom]]: expected value of type [string] but got [number]]` ); }); + + it('do not throw on unknown overrides', async () => { + const coreContext = mockCoreContext.create(); + coreContext.configService.atPath.mockReturnValueOnce( + new BehaviorSubject({ + overrides: { + custom: 42, + }, + }) + ); + const customizedService = new UiSettingsService(coreContext); + await customizedService.setup(setupDeps); + + await customizedService.start(); + }); }); describe('#asScopedToClient', () => { diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 25062490f5b6b..4f757d18ea7da 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -109,15 +109,17 @@ export class UiSettingsService private validatesDefinitions() { for (const [key, definition] of this.uiSettingsDefaults) { - if (definition.schema) { - definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); + if (!definition.schema) { + throw new Error(`Validation schema is not provided for [${key}] UI Setting`); } + definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); } } private validatesOverrides() { for (const [key, value] of Object.entries(this.overrides)) { const definition = this.uiSettingsDefaults.get(key); + // overrides might contain UiSettings for a disabled plugin if (definition?.schema) { definition.schema.validate(value, {}, `ui settings overrides [${key}]`); } From ccb0b354525a072c9b637f1147637391fdf8fa0b Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 10 Nov 2020 13:45:46 -0500 Subject: [PATCH 62/87] [Lens] Use entire layers, not specific columns (#82550) * [Lens] Use entire layers, not specific columns * Fix types * Move all of state_helpers over * Fix tests * Fix crash and add tests to prevent future issues * Prevent users from dropping duplicate fields * Respond to review feedback * Fix review feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../config_panel/layer_panel.test.tsx | 37 + .../editor_frame/config_panel/layer_panel.tsx | 19 +- .../__mocks__/state_helpers.ts | 21 - .../bucket_nesting_editor.test.tsx | 61 +- .../dimension_panel/dimension_editor.tsx | 107 +- .../dimension_panel/dimension_panel.test.tsx | 23 +- .../dimension_panel/droppable.test.ts | 280 ++--- .../dimension_panel/droppable.ts | 71 +- .../indexpattern_datasource/indexpattern.tsx | 9 +- .../indexpattern_suggestions.test.tsx | 105 +- .../indexpattern_suggestions.ts | 400 +++---- .../layerpanel.test.tsx | 2 - .../public/indexpattern_datasource/loader.ts | 2 +- .../operations/__mocks__/index.ts | 27 +- .../operations/definitions/cardinality.tsx | 10 +- .../operations/definitions/column_types.ts | 3 +- .../operations/definitions/count.tsx | 10 +- .../definitions/date_histogram.test.tsx | 10 +- .../operations/definitions/date_histogram.tsx | 17 +- .../definitions/filters/filters.tsx | 5 +- .../operations/definitions/index.ts | 15 +- .../operations/definitions/metrics.tsx | 5 +- .../operations/definitions/ranges/ranges.tsx | 37 +- .../operations/definitions/terms/index.tsx | 12 +- .../definitions/terms/terms.test.tsx | 38 +- .../operations/index.ts | 1 + .../layer_helpers.test.ts} | 1008 +++++++++++------ .../operations/layer_helpers.ts | 344 ++++++ .../operations/operations.test.ts | 72 +- .../operations/operations.ts | 97 +- .../indexpattern_datasource/state_helpers.ts | 180 +-- x-pack/plugins/lens/public/types.ts | 8 - .../public/xy_visualization/visualization.tsx | 2 - x-pack/plugins/lens/server/migrations.test.ts | 88 +- x-pack/plugins/lens/server/migrations.ts | 30 +- 35 files changed, 1710 insertions(+), 1446 deletions(-) delete mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts rename x-pack/plugins/lens/public/indexpattern_datasource/{state_helpers.test.ts => operations/layer_helpers.test.ts} (52%) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 56425326c1ce1..c0cd211a49dd9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -371,6 +371,43 @@ describe('LayerPanel', () => { ); }); + it('should determine if the datasource supports dropping of a field onto a pre-filled dimension', () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: ['a'], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + mockDatasource.canHandleDrop.mockImplementation(({ columnId }) => columnId !== 'a'); + + const draggingField = { field: { name: 'dragged' }, indexPatternId: 'a', id: '1' }; + + const component = mountWithIntl( + + + + ); + + expect(mockDatasource.canHandleDrop).toHaveBeenCalledWith( + expect.objectContaining({ columnId: 'a' }) + ); + + expect( + component.find('DragDrop[data-test-subj="lnsGroup"]').first().prop('droppable') + ).toEqual(false); + + component.find('DragDrop[data-test-subj="lnsGroup"]').first().simulate('drop'); + + expect(mockDatasource.onDrop).not.toHaveBeenCalled(); + }); + it('should allow drag to move between groups', () => { (generateId as jest.Mock).mockReturnValue(`newid`); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 0332f11aa78b3..f780f9c3f22d7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -235,6 +235,17 @@ export function LayerPanel( dragging.groupId === group.groupId && dragging.columnId !== accessor && dragging.groupId !== 'y'; // TODO: remove this line when https://github.com/elastic/elastic-charts/issues/868 is fixed + + const isDroppable = isDraggedOperation(dragging) + ? dragType === 'reorder' + ? isFromTheSameGroup + : isFromCompatibleGroup + : layerDatasource.canHandleDrop({ + ...layerDatasourceDropProps, + columnId: accessor, + filterOperations: group.filterOperations, + }); + return ( { layerDatasource.onDrop({ isReorder: true, @@ -303,7 +310,6 @@ export function LayerPanel( ...layerDatasourceConfigProps, columnId: accessor, filterOperations: group.filterOperations, - suggestedPriority: group.suggestedPriority, onClick: () => { if (activeId) { setActiveDimension(initialActiveDimensionState); @@ -450,7 +456,6 @@ export function LayerPanel( core: props.core, columnId: activeId, filterOperations: activeGroup.filterOperations, - suggestedPriority: activeGroup?.suggestedPriority, dimensionGroups: groups, setState: (newState: unknown) => { props.updateAll( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts deleted file mode 100644 index 47687ef10f882..0000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/__mocks__/state_helpers.ts +++ /dev/null @@ -1,21 +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. - */ - -const actual = jest.requireActual('../state_helpers'); - -jest.spyOn(actual, 'changeColumn'); -jest.spyOn(actual, 'updateLayerIndexPattern'); - -export const { - getColumnOrder, - changeColumn, - deleteColumn, - updateColumnParam, - sortByField, - hasField, - updateLayerIndexPattern, - mergeLayer, -} = actual; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx index ee6a86072236c..ef0b7f1554478 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx @@ -31,7 +31,6 @@ describe('BucketNestingEditor', () => { orderDirection: 'asc', }, sourceField: 'a', - suggestedPriority: 0, ...col, }; @@ -46,9 +45,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['a', 'b', 'c'], columns: { - a: mockCol({ suggestedPriority: 0 }), - b: mockCol({ suggestedPriority: 1 }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol(), + b: mockCol(), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }} @@ -67,9 +66,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['b', 'a', 'c'], columns: { - a: mockCol({ suggestedPriority: 0 }), - b: mockCol({ suggestedPriority: 1 }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol(), + b: mockCol(), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }} @@ -89,9 +88,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['b', 'a', 'c'], columns: { - a: mockCol({ suggestedPriority: 0 }), - b: mockCol({ suggestedPriority: 1 }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol(), + b: mockCol(), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }} @@ -109,9 +108,9 @@ describe('BucketNestingEditor', () => { layer: { columnOrder: ['a', 'b', 'c'], columns: { - a: mockCol({ suggestedPriority: 0 }), - b: mockCol({ suggestedPriority: 1 }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol(), + b: mockCol(), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }, @@ -134,9 +133,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['a', 'b', 'c'], columns: { - a: mockCol({ suggestedPriority: 0, operationType: 'avg', isBucketed: false }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: false }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol({ operationType: 'avg', isBucketed: false }), + b: mockCol({ operationType: 'max', isBucketed: false }), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }} @@ -155,9 +154,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['a', 'b', 'c'], columns: { - a: mockCol({ suggestedPriority: 0 }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: false }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: false }), + a: mockCol(), + b: mockCol({ operationType: 'max', isBucketed: false }), + c: mockCol({ operationType: 'min', isBucketed: false }), }, indexPatternId: 'foo', }} @@ -176,9 +175,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['c', 'a', 'b'], columns: { - a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }), + a: mockCol({ operationType: 'count', isBucketed: true }), + b: mockCol({ operationType: 'max', isBucketed: true }), + c: mockCol({ operationType: 'min', isBucketed: true }), }, indexPatternId: 'foo', }} @@ -200,9 +199,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['c', 'a', 'b'], columns: { - a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }), + a: mockCol({ operationType: 'count', isBucketed: true }), + b: mockCol({ operationType: 'max', isBucketed: true }), + c: mockCol({ operationType: 'min', isBucketed: true }), }, indexPatternId: 'foo', }} @@ -227,9 +226,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['c', 'a', 'b'], columns: { - a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }), + a: mockCol({ operationType: 'count', isBucketed: true }), + b: mockCol({ operationType: 'max', isBucketed: true }), + c: mockCol({ operationType: 'min', isBucketed: true }), }, indexPatternId: 'foo', }} @@ -254,9 +253,9 @@ describe('BucketNestingEditor', () => { layer={{ columnOrder: ['c', 'a', 'b'], columns: { - a: mockCol({ suggestedPriority: 0, operationType: 'count', isBucketed: true }), - b: mockCol({ suggestedPriority: 1, operationType: 'max', isBucketed: true }), - c: mockCol({ suggestedPriority: 2, operationType: 'min', isBucketed: true }), + a: mockCol({ operationType: 'count', isBucketed: true }), + b: mockCol({ operationType: 'max', isBucketed: true }), + c: mockCol({ operationType: 'min', isBucketed: true }), }, indexPatternId: 'foo', }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 7cbfbc1749382..cd196745f3315 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -22,14 +22,16 @@ import { IndexPatternColumn, OperationType } from '../indexpattern'; import { operationDefinitionMap, getOperationDisplay, - buildColumn, - changeField, + insertOrReplaceColumn, + replaceColumn, + deleteColumn, + updateColumnParam, } from '../operations'; -import { deleteColumn, changeColumn, updateColumnParam, mergeLayer } from '../state_helpers'; +import { mergeLayer } from '../state_helpers'; import { FieldSelect } from './field_select'; import { hasField, fieldIsInvalid } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; -import { IndexPattern } from '../types'; +import { IndexPattern, IndexPatternLayer } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; @@ -170,21 +172,13 @@ export function DimensionEditor(props: DimensionEditorProps) { if (selectedColumn?.operationType === operationType) { return; } - setState( - changeColumn({ - state, - layerId, - columnId, - newColumn: buildColumn({ - columns: props.state.layers[props.layerId].columns, - suggestedPriority: props.suggestedPriority, - layerId: props.layerId, - op: operationType, - indexPattern: currentIndexPattern, - previousColumn: selectedColumn, - }), - }) - ); + const newLayer = insertOrReplaceColumn({ + layer: props.state.layers[props.layerId], + indexPattern: currentIndexPattern, + columnId, + op: operationType, + }); + setState(mergeLayer({ state, layerId, newLayer })); trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } else if (!selectedColumn || !compatibleWithCurrentField) { @@ -192,18 +186,15 @@ export function DimensionEditor(props: DimensionEditorProps) { if (possibleFields.size === 1) { setState( - changeColumn({ + mergeLayer({ state, layerId, - columnId, - newColumn: buildColumn({ - columns: props.state.layers[props.layerId].columns, - suggestedPriority: props.suggestedPriority, - layerId: props.layerId, - op: operationType, + newLayer: insertOrReplaceColumn({ + layer: props.state.layers[props.layerId], indexPattern: currentIndexPattern, + columnId, + op: operationType, field: currentIndexPattern.getFieldByName(possibleFields.values().next().value), - previousColumn: selectedColumn, }), }) ); @@ -216,30 +207,21 @@ export function DimensionEditor(props: DimensionEditorProps) { setInvalidOperationType(null); - if (selectedColumn?.operationType === operationType) { + if (selectedColumn.operationType === operationType) { return; } - const newColumn: IndexPatternColumn = buildColumn({ - columns: props.state.layers[props.layerId].columns, - suggestedPriority: props.suggestedPriority, - layerId: props.layerId, - op: operationType, + const newLayer = replaceColumn({ + layer: props.state.layers[props.layerId], indexPattern: currentIndexPattern, + columnId, + op: operationType, field: hasField(selectedColumn) ? currentIndexPattern.getFieldByName(selectedColumn.sourceField) : undefined, - previousColumn: selectedColumn, }); - setState( - changeColumn({ - state, - layerId, - columnId, - newColumn, - }) - ); + setState(mergeLayer({ state, layerId, newLayer })); }, }; } @@ -297,30 +279,31 @@ export function DimensionEditor(props: DimensionEditorProps) { incompatibleSelectedOperationType={incompatibleSelectedOperationType} onDeleteColumn={() => { setState( - deleteColumn({ + mergeLayer({ state, layerId, - columnId, + newLayer: deleteColumn({ layer: state.layers[layerId], columnId }), }) ); }} onChoose={(choice) => { - let column: IndexPatternColumn; + let newLayer: IndexPatternLayer; if ( !incompatibleSelectedOperationType && selectedColumn && 'field' in choice && choice.operationType === selectedColumn.operationType ) { - // If we just changed the field are not in an error state and the operation didn't change, - // we use the operations onFieldChange method to calculate the new column. - column = changeField( - selectedColumn, - currentIndexPattern, - currentIndexPattern.getFieldByName(choice.field)! - ); + // Replaces just the field + newLayer = replaceColumn({ + layer: state.layers[layerId], + columnId, + indexPattern: currentIndexPattern, + op: choice.operationType, + field: currentIndexPattern.getFieldByName(choice.field)!, + }); } else { - // Otherwise we'll use the buildColumn method to calculate a new column + // Finds a new operation const compatibleOperations = ('field' in choice && operationSupportMatrix.operationByField[choice.field]) || new Set(); @@ -334,26 +317,16 @@ export function DimensionEditor(props: DimensionEditorProps) { } else if ('field' in choice) { operation = choice.operationType; } - column = buildColumn({ - columns: props.state.layers[props.layerId].columns, + newLayer = insertOrReplaceColumn({ + layer: state.layers[layerId], + columnId, field: currentIndexPattern.getFieldByName(choice.field), indexPattern: currentIndexPattern, - layerId: props.layerId, - suggestedPriority: props.suggestedPriority, op: operation as OperationType, - previousColumn: selectedColumn, }); } - setState( - changeColumn({ - state, - layerId, - columnId, - newColumn: column, - keepParams: false, - }) - ); + setState(mergeLayer({ state, layerId, newLayer })); setInvalidOperationType(null); }} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 3ed04b08df58f..e9eb3fa4542fe 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { EuiComboBox, EuiListGroupItemProps, EuiListGroup, EuiRange } from '@elastic/eui'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; -import { changeColumn } from '../state_helpers'; import { IndexPatternDimensionEditorComponent, IndexPatternDimensionEditorProps, @@ -18,14 +17,14 @@ import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/e import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IndexPatternPrivateState } from '../types'; -import { IndexPatternColumn } from '../operations'; +import { IndexPatternColumn, replaceColumn } from '../operations'; import { documentField } from '../document_field'; import { OperationMetadata } from '../../types'; import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram'; import { getFieldByNameFactory } from '../pure_helpers'; jest.mock('../loader'); -jest.mock('../state_helpers'); +jest.mock('../operations'); jest.mock('lodash', () => { const original = jest.requireActual('lodash'); @@ -682,7 +681,7 @@ describe('IndexPatternDimensionEditorPanel', () => { // Other parts of this don't matter for this test }), }, - columnOrder: ['col1', 'col2'], + columnOrder: ['col2', 'col1'], }, }, }); @@ -1029,15 +1028,13 @@ describe('IndexPatternDimensionEditorPanel', () => { ); }); - expect(changeColumn).toHaveBeenCalledWith({ - state: initialState, - columnId: 'col1', - layerId: 'first', - newColumn: expect.objectContaining({ - sourceField: 'bytes', - operationType: 'min', - }), - }); + expect(replaceColumn).toHaveBeenCalledWith( + expect.objectContaining({ + columnId: 'col1', + op: 'min', + field: expect.objectContaining({ name: 'bytes' }), + }) + ); }); it('should clear the dimension when removing the selection in field combobox', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index 1d85c1f8f78ca..48240a5417108 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -17,7 +17,7 @@ import { OperationMetadata } from '../../types'; import { IndexPatternColumn } from '../operations'; import { getFieldByNameFactory } from '../pure_helpers'; -jest.mock('../state_helpers'); +jest.mock('../operations'); const fields = [ { @@ -56,8 +56,8 @@ const fields = [ ]; const expectedIndexPatterns = { - 1: { - id: '1', + foo: { + id: 'foo', title: 'my-fake-index-pattern', timeFieldName: 'timestamp', hasExistence: true, @@ -89,7 +89,7 @@ describe('IndexPatternDimensionEditorPanel', () => { state = { indexPatternRefs: [], indexPatterns: expectedIndexPatterns, - currentIndexPatternId: '1', + currentIndexPatternId: 'foo', isFirstExistenceFetch: false, existingFields: { 'my-fake-index-pattern': { @@ -101,7 +101,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }, layers: { first: { - indexPatternId: '1', + indexPatternId: 'foo', columnOrder: ['col1'], columns: { col1: { @@ -156,84 +156,8 @@ describe('IndexPatternDimensionEditorPanel', () => { jest.clearAllMocks(); }); - function dragDropState(): IndexPatternPrivateState { - return { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: { - foo: { - id: 'foo', - title: 'Foo pattern', - hasRestrictions: false, - fields: [ - { - aggregatable: true, - name: 'bar', - displayName: 'bar', - searchable: true, - type: 'number', - }, - { - aggregatable: true, - name: 'mystring', - displayName: 'mystring', - searchable: true, - type: 'string', - }, - ], - - getFieldByName: getFieldByNameFactory([ - { - aggregatable: true, - name: 'bar', - displayName: 'bar', - searchable: true, - type: 'number', - }, - { - aggregatable: true, - name: 'mystring', - displayName: 'mystring', - searchable: true, - type: 'string', - }, - ]), - }, - }, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - myLayer: { - indexPatternId: 'foo', - columnOrder: ['col1'], - columns: { - col1: { - label: 'Date histogram of timestamp', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - params: { - interval: '1d', - }, - sourceField: 'timestamp', - }, - }, - }, - }, - }; - } - it('is not droppable if no drag is happening', () => { - expect( - canHandleDrop({ - ...defaultProps, - dragDropContext, - state: dragDropState(), - layerId: 'myLayer', - }) - ).toBe(false); + expect(canHandleDrop({ ...defaultProps, dragDropContext })).toBe(false); }); it('is not droppable if the dragged item has no field', () => { @@ -260,9 +184,7 @@ describe('IndexPatternDimensionEditorPanel', () => { id: 'mystring', }, }, - state: dragDropState(), filterOperations: () => false, - layerId: 'myLayer', }) ).toBe(false); }); @@ -274,14 +196,12 @@ describe('IndexPatternDimensionEditorPanel', () => { dragDropContext: { ...dragDropContext, dragging: { - field: { type: 'number', name: 'bar', aggregatable: true }, + field: { type: 'number', name: 'bytes', aggregatable: true }, indexPatternId: 'foo', id: 'bar', }, }, - state: dragDropState(), filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', }) ).toBe(true); }); @@ -298,9 +218,30 @@ describe('IndexPatternDimensionEditorPanel', () => { id: 'bar', }, }, - state: dragDropState(), filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', + }) + ).toBe(false); + }); + + it('is not droppable if the dragged field is already in use by this operation', () => { + expect( + canHandleDrop({ + ...defaultProps, + dragDropContext: { + ...dragDropContext, + dragging: { + field: { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + exists: true, + }, + indexPatternId: 'foo', + id: 'bar', + }, + }, }) ).toBe(false); }); @@ -314,14 +255,11 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging: { columnId: 'col1', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col1', }, }, - state: dragDropState(), columnId: 'col2', - filterOperations: (op: OperationMetadata) => true, - layerId: 'myLayer', }) ).toBe(true); }); @@ -335,14 +273,10 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging: { columnId: 'col1', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'bar', }, }, - state: dragDropState(), - columnId: 'col1', - filterOperations: (op: OperationMetadata) => true, - layerId: 'myLayer', }) ).toBe(false); }); @@ -356,25 +290,22 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging: { columnId: 'col1', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'bar', }, }, - state: dragDropState(), columnId: 'col2', filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', }) ).toBe(false); }); it('appends the dropped column when a field is dropped', () => { const dragging = { - field: { type: 'number', name: 'bar', aggregatable: true }, + field: { type: 'number', name: 'bytes', aggregatable: true }, indexPatternId: 'foo', id: 'bar', }; - const testState = dragDropState(); onDrop({ ...defaultProps, @@ -383,24 +314,22 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging, }, droppedItem: dragging, - state: testState, columnId: 'col2', filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ - ...testState, + ...state, layers: { - myLayer: { - ...testState.layers.myLayer, + first: { + ...state.layers.first, columnOrder: ['col1', 'col2'], columns: { - ...testState.layers.myLayer.columns, + ...state.layers.first.columns, col2: expect.objectContaining({ dataType: 'number', - sourceField: 'bar', + sourceField: 'bytes', }), }, }, @@ -410,11 +339,10 @@ describe('IndexPatternDimensionEditorPanel', () => { it('selects the specific operation that was valid on drop', () => { const dragging = { - field: { type: 'string', name: 'mystring', aggregatable: true }, + field: { type: 'string', name: 'source', aggregatable: true }, indexPatternId: 'foo', id: 'bar', }; - const testState = dragDropState(); onDrop({ ...defaultProps, dragDropContext: { @@ -422,24 +350,22 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging, }, droppedItem: dragging, - state: testState, columnId: 'col2', filterOperations: (op: OperationMetadata) => op.isBucketed, - layerId: 'myLayer', }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ - ...testState, + ...state, layers: { - myLayer: { - ...testState.layers.myLayer, - columnOrder: ['col1', 'col2'], + first: { + ...state.layers.first, + columnOrder: ['col2', 'col1'], columns: { - ...testState.layers.myLayer.columns, + ...state.layers.first.columns, col2: expect.objectContaining({ dataType: 'string', - sourceField: 'mystring', + sourceField: 'source', }), }, }, @@ -449,11 +375,10 @@ describe('IndexPatternDimensionEditorPanel', () => { it('updates a column when a field is dropped', () => { const dragging = { - field: { type: 'number', name: 'bar', aggregatable: true }, + field: { type: 'number', name: 'bytes', aggregatable: true }, indexPatternId: 'foo', id: 'bar', }; - const testState = dragDropState(); onDrop({ ...defaultProps, dragDropContext: { @@ -461,20 +386,18 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging, }, droppedItem: dragging, - state: testState, filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ - ...testState, + ...state, layers: { - myLayer: expect.objectContaining({ + first: expect.objectContaining({ columns: expect.objectContaining({ col1: expect.objectContaining({ dataType: 'number', - sourceField: 'bar', + sourceField: 'bytes', }), }), }), @@ -482,13 +405,12 @@ describe('IndexPatternDimensionEditorPanel', () => { }); }); - it('does not set the size of the terms aggregation', () => { + it('keeps the operation when dropping a different compatible field', () => { const dragging = { - field: { type: 'string', name: 'mystring', aggregatable: true }, + field: { name: 'memory', type: 'number', aggregatable: true }, indexPatternId: 'foo', - id: 'bar', + id: '1', }; - const testState = dragDropState(); onDrop({ ...defaultProps, dragDropContext: { @@ -496,27 +418,41 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging, }, droppedItem: dragging, - state: testState, - columnId: 'col2', - filterOperations: (op: OperationMetadata) => op.isBucketed, - layerId: 'myLayer', + state: { + ...state, + layers: { + first: { + indexPatternId: 'foo', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Sum of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'sum', + sourceField: 'bytes', + }, + }, + }, + }, + }, }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ - ...testState, + ...state, layers: { - myLayer: { - ...testState.layers.myLayer, - columnOrder: ['col1', 'col2'], - columns: { - ...testState.layers.myLayer.columns, - col2: expect.objectContaining({ - operationType: 'terms', - params: expect.objectContaining({ size: 3 }), + first: expect.objectContaining({ + columns: expect.objectContaining({ + col1: expect.objectContaining({ + operationType: 'sum', + dataType: 'number', + sourceField: 'memory', }), - }, - }, + }), + }), }, }); }); @@ -525,10 +461,9 @@ describe('IndexPatternDimensionEditorPanel', () => { const dragging = { columnId: 'col1', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'bar', }; - const testState = dragDropState(); onDrop({ ...defaultProps, @@ -537,21 +472,18 @@ describe('IndexPatternDimensionEditorPanel', () => { dragging, }, droppedItem: dragging, - state: testState, columnId: 'col2', - filterOperations: (op: OperationMetadata) => true, - layerId: 'myLayer', }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ - ...testState, + ...state, layers: { - myLayer: { - ...testState.layers.myLayer, + first: { + ...state.layers.first, columnOrder: ['col2'], columns: { - col2: testState.layers.myLayer.columns.col1, + col2: state.layers.first.columns.col1, }, }, }, @@ -562,15 +494,15 @@ describe('IndexPatternDimensionEditorPanel', () => { const dragging = { columnId: 'col2', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col2', }; - const testState = dragDropState(); - testState.layers.myLayer = { + const testState = { ...state }; + testState.layers.first = { indexPatternId: 'foo', columnOrder: ['col1', 'col2', 'col3'], columns: { - col1: testState.layers.myLayer.columns.col1, + col1: testState.layers.first.columns.col1, col2: { label: 'Top values of src', @@ -606,21 +538,18 @@ describe('IndexPatternDimensionEditorPanel', () => { }, droppedItem: dragging, state: testState, - columnId: 'col1', - filterOperations: (op: OperationMetadata) => true, - layerId: 'myLayer', }); expect(setState).toBeCalledTimes(1); expect(setState).toHaveBeenCalledWith({ ...testState, layers: { - myLayer: { - ...testState.layers.myLayer, + first: { + ...testState.layers.first, columnOrder: ['col1', 'col3'], columns: { - col1: testState.layers.myLayer.columns.col2, - col3: testState.layers.myLayer.columns.col3, + col1: testState.layers.first.columns.col2, + col3: testState.layers.first.columns.col3, }, }, }, @@ -631,13 +560,13 @@ describe('IndexPatternDimensionEditorPanel', () => { const dragging = { columnId: 'col1', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col1', }; const testState = { - ...dragDropState(), + ...state, layers: { - myLayer: { + first: { indexPatternId: 'foo', columnOrder: ['col1', 'col2', 'col3'], columns: { @@ -671,18 +600,17 @@ describe('IndexPatternDimensionEditorPanel', () => { droppedItem: dragging, state: testState, filterOperations: (op: OperationMetadata) => op.dataType === 'number', - layerId: 'myLayer', }; const stateWithColumnOrder = (columnOrder: string[]) => { return { ...testState, layers: { - myLayer: { - ...testState.layers.myLayer, + first: { + ...testState.layers.first, columnOrder, columns: { - ...testState.layers.myLayer.columns, + ...testState.layers.first.columns, }, }, }, @@ -704,7 +632,7 @@ describe('IndexPatternDimensionEditorPanel', () => { droppedItem: { columnId: 'col3', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col3', }, }); @@ -718,7 +646,7 @@ describe('IndexPatternDimensionEditorPanel', () => { droppedItem: { columnId: 'col2', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col2', }, }); @@ -732,7 +660,7 @@ describe('IndexPatternDimensionEditorPanel', () => { droppedItem: { columnId: 'col2', groupId: 'a', - layerId: 'myLayer', + layerId: 'first', id: 'col2', }, }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index a6ff550af96e9..e4eabafc6938e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -10,9 +10,9 @@ import { isDraggedOperation, } from '../../types'; import { IndexPatternColumn } from '../indexpattern'; -import { buildColumn, changeField } from '../operations'; -import { changeColumn, mergeLayer } from '../state_helpers'; -import { isDraggedField, hasField } from '../utils'; +import { insertOrReplaceColumn } from '../operations'; +import { mergeLayer } from '../state_helpers'; +import { hasField, isDraggedField } from '../utils'; import { IndexPatternPrivateState, IndexPatternField } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { getOperationSupportMatrix } from './operation_support'; @@ -28,9 +28,12 @@ export function canHandleDrop(props: DatasourceDimensionDropProps columns.length); trackUiEvent(hasData ? 'drop_non_empty' : 'drop_empty'); - - setState( - changeColumn({ - state, - layerId, - columnId, - newColumn, - // If the field has changed, the onFieldChange method needs to take care of everything including moving - // over params. If we create a new column above we want changeColumn to move over params. - keepParams: !hasFieldChanged, - }) - ); + setState(mergeLayer({ state, layerId, newLayer })); return true; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index fa106e90d518a..e37c31559cd0c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -51,13 +51,14 @@ import { IndexPatternField, IndexPatternPrivateState, IndexPatternPersistedState import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; -import { deleteColumn } from './state_helpers'; +import { mergeLayer } from './state_helpers'; import { Datasource, StateSetter } from '../index'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import { deleteColumn } from './operations'; import { FieldBasedIndexPatternColumn } from './operations/definitions/column_types'; import { Dragging } from '../drag_drop/providers'; -export { OperationType, IndexPatternColumn } from './operations'; +export { OperationType, IndexPatternColumn, deleteColumn } from './operations'; export type DraggedField = Dragging & { field: IndexPatternField; @@ -159,10 +160,10 @@ export function getIndexPatternDatasource({ }, removeColumn({ prevState, layerId, columnId }) { - return deleteColumn({ + return mergeLayer({ state: prevState, layerId, - columnId, + newLayer: deleteColumn({ layer: prevState.layers[layerId], columnId }), }); }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 523a1be34ba3d..c88af50e525d4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -12,6 +12,7 @@ import { getDatasourceSuggestionsFromCurrentState, getDatasourceSuggestionsForVisualizeField, } from './indexpattern_suggestions'; +import { documentField } from './document_field'; import { getFieldByNameFactory } from './pure_helpers'; jest.mock('./loader'); @@ -60,6 +61,7 @@ const fieldsOne = [ aggregatable: true, searchable: true, }, + documentField, ]; const fieldsTwo = [ @@ -116,6 +118,7 @@ const fieldsTwo = [ }, }, }, + documentField, ]; const expectedIndexPatterns = { @@ -290,13 +293,13 @@ describe('IndexPattern Data Source suggestions', () => { state: expect.objectContaining({ layers: { id1: expect.objectContaining({ - columnOrder: ['id2', 'id3'], + columnOrder: ['id3', 'id2'], columns: { - id2: expect.objectContaining({ + id3: expect.objectContaining({ operationType: 'date_histogram', sourceField: 'timestamp', }), - id3: expect.objectContaining({ + id2: expect.objectContaining({ operationType: 'avg', sourceField: 'bytes', }), @@ -310,10 +313,10 @@ describe('IndexPattern Data Source suggestions', () => { isMultiRow: true, columns: [ expect.objectContaining({ - columnId: 'id2', + columnId: 'id3', }), expect.objectContaining({ - columnId: 'id3', + columnId: 'id2', }), ], layerId: 'id1', @@ -510,13 +513,13 @@ describe('IndexPattern Data Source suggestions', () => { state: expect.objectContaining({ layers: { previousLayer: expect.objectContaining({ - columnOrder: ['id1', 'id2'], + columnOrder: ['id2', 'id1'], columns: { - id1: expect.objectContaining({ + id2: expect.objectContaining({ operationType: 'date_histogram', sourceField: 'timestamp', }), - id2: expect.objectContaining({ + id1: expect.objectContaining({ operationType: 'avg', sourceField: 'bytes', }), @@ -530,10 +533,10 @@ describe('IndexPattern Data Source suggestions', () => { isMultiRow: true, columns: [ expect.objectContaining({ - columnId: 'id1', + columnId: 'id2', }), expect.objectContaining({ - columnId: 'id2', + columnId: 'id1', }), ], layerId: 'previousLayer', @@ -757,9 +760,9 @@ describe('IndexPattern Data Source suggestions', () => { layers: { previousLayer: initialState.layers.previousLayer, currentLayer: expect.objectContaining({ - columnOrder: ['id1', 'colb'], + columnOrder: ['cola', 'colb'], columns: { - id1: expect.objectContaining({ + cola: expect.objectContaining({ operationType: 'date_histogram', sourceField: 'start_date', }), @@ -867,7 +870,7 @@ describe('IndexPattern Data Source suggestions', () => { ); }); - it('replaces a metric column on a number field if only one other metric is already set', () => { + it('suggests both replacing and adding metric if only one other metric is set', () => { const initialState = stateWithNonEmptyTables(); const suggestions = getDatasourceSuggestionsForField(initialState, '1', { name: 'memory', @@ -895,6 +898,26 @@ describe('IndexPattern Data Source suggestions', () => { }), }) ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: expect.objectContaining({ + currentLayer: expect.objectContaining({ + columnOrder: ['cola', 'colb', 'id1'], + columns: { + cola: initialState.layers.currentLayer.columns.cola, + colb: initialState.layers.currentLayer.columns.colb, + id1: expect.objectContaining({ + operationType: 'avg', + sourceField: 'memory', + }), + }, + }), + }), + }), + }) + ); }); it('adds a metric column on a number field if no other metrics set', () => { @@ -941,7 +964,20 @@ describe('IndexPattern Data Source suggestions', () => { ); }); - it('adds a metric column on a number field if 2 or more other metric', () => { + it('skips duplicates when the field is already in use', () => { + const initialState = stateWithNonEmptyTables(); + const suggestions = getDatasourceSuggestionsForField(initialState, '1', { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }); + + expect(suggestions).not.toContain(expect.objectContaining({ changeType: 'extended' })); + }); + + it('skips duplicates when the document-specific field is already in use', () => { const initialState = stateWithNonEmptyTables(); const modifiedState: IndexPatternPrivateState = { ...initialState, @@ -951,45 +987,20 @@ describe('IndexPattern Data Source suggestions', () => { ...initialState.layers.currentLayer, columns: { ...initialState.layers.currentLayer.columns, - colc: { - dataType: 'number', + colb: { + label: 'Count of records', + dataType: 'document', isBucketed: false, - sourceField: 'dest', - label: 'Unique count of dest', - operationType: 'cardinality', + + operationType: 'count', + sourceField: 'Records', }, }, - columnOrder: ['cola', 'colb', 'colc'], }, }, }; - const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', { - name: 'memory', - displayName: 'memory', - type: 'number', - aggregatable: true, - searchable: true, - }); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: { - previousLayer: modifiedState.layers.previousLayer, - currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'colb', 'colc', 'id1'], - columns: { - ...modifiedState.layers.currentLayer.columns, - id1: expect.objectContaining({ - operationType: 'avg', - sourceField: 'memory', - }), - }, - }), - }, - }), - }) - ); + const suggestions = getDatasourceSuggestionsForField(modifiedState, '1', documentField); + expect(suggestions).not.toContain(expect.objectContaining({ changeType: 'extended' })); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index c12d7d4be226b..b74d75207e112 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -10,14 +10,14 @@ import { generateId } from '../id_generator'; import { DatasourceSuggestion, TableChangeType } from '../types'; import { columnToOperation } from './indexpattern'; import { - buildColumn, + insertNewColumn, + replaceColumn, + getMetricOperationTypes, getOperationTypesForField, operationDefinitionMap, IndexPatternColumn, OperationType, } from './operations'; -import { operationDefinitions } from './operations/definitions'; -import { TermsIndexPatternColumn } from './operations/definitions/terms'; import { hasField, hasInvalidReference } from './utils'; import { IndexPattern, @@ -137,6 +137,7 @@ export function getDatasourceSuggestionsForVisualizeField( ); } +// TODO: Stop hard-coding the specific operation types function getBucketOperation(field: IndexPatternField) { // We allow numeric bucket types in some cases, but it's generally not the right suggestion, // so we eliminate it here. @@ -160,29 +161,38 @@ function getExistingLayerSuggestionsForField( const suggestions: IndexPatternSugestion[] = []; if (usableAsBucketOperation && !fieldInUse) { - suggestions.push( - buildSuggestion({ - state, - updatedLayer: addFieldAsBucketOperation( - layer, + if ( + usableAsBucketOperation === 'date_histogram' && + layer.columnOrder.some((colId) => layer.columns[colId].operationType === 'date_histogram') + ) { + const previousDate = layer.columnOrder.find( + (colId) => layer.columns[colId].operationType === 'date_histogram' + )!; + suggestions.push( + buildSuggestion({ + state, + updatedLayer: replaceColumn({ + layer, + indexPattern, + field, + op: usableAsBucketOperation, + columnId: previousDate, + }), layerId, - indexPattern, - field, - usableAsBucketOperation - ), - layerId, - changeType: 'extended', - }) - ); - } - - if (!usableAsBucketOperation && operations.length > 0) { - const updatedLayer = addFieldAsMetricOperation(layer, layerId, indexPattern, field); - if (updatedLayer) { + changeType: 'initial', + }) + ); + } else { suggestions.push( buildSuggestion({ state, - updatedLayer, + updatedLayer: insertNewColumn({ + layer, + indexPattern, + field, + op: usableAsBucketOperation, + columnId: generateId(), + }), layerId, changeType: 'extended', }) @@ -190,6 +200,50 @@ function getExistingLayerSuggestionsForField( } } + if (!usableAsBucketOperation && operations.length > 0 && !fieldInUse) { + const [metricOperation] = getMetricOperationTypes(field); + if (metricOperation) { + const layerWithNewMetric = insertNewColumn({ + layer, + indexPattern, + field, + columnId: generateId(), + op: metricOperation.type, + }); + if (layerWithNewMetric) { + suggestions.push( + buildSuggestion({ + state, + layerId, + updatedLayer: layerWithNewMetric, + changeType: 'extended', + }) + ); + } + + const [, metrics] = separateBucketColumns(layer); + if (metrics.length === 1) { + const layerWithReplacedMetric = replaceColumn({ + layer, + indexPattern, + field, + columnId: metrics[0], + op: metricOperation.type, + }); + if (layerWithReplacedMetric) { + suggestions.push( + buildSuggestion({ + state, + layerId, + updatedLayer: layerWithReplacedMetric, + changeType: 'extended', + }) + ); + } + } + } + } + const metricSuggestion = createMetricSuggestion(indexPattern, layerId, state, field); if (metricSuggestion) { suggestions.push(metricSuggestion); @@ -198,100 +252,6 @@ function getExistingLayerSuggestionsForField( return suggestions; } -function addFieldAsMetricOperation( - layer: IndexPatternLayer, - layerId: string, - indexPattern: IndexPattern, - field: IndexPatternField -): IndexPatternLayer | undefined { - const newColumn = getMetricColumn(indexPattern, layerId, field); - const addedColumnId = generateId(); - - const [, metrics] = separateBucketColumns(layer); - - // Add metrics if there are 0 or > 1 metric - if (metrics.length !== 1) { - return { - indexPatternId: indexPattern.id, - columns: { - ...layer.columns, - [addedColumnId]: newColumn, - }, - columnOrder: [...layer.columnOrder, addedColumnId], - }; - } - - // Replacing old column with new column, keeping the old ID - const newColumns = { ...layer.columns, [metrics[0]]: newColumn }; - - return { - indexPatternId: indexPattern.id, - columns: newColumns, - columnOrder: layer.columnOrder, // Order is kept by replacing - }; -} - -function addFieldAsBucketOperation( - layer: IndexPatternLayer, - layerId: string, - indexPattern: IndexPattern, - field: IndexPatternField, - operation: OperationType -): IndexPatternLayer { - const newColumn = buildColumn({ - op: operation, - columns: layer.columns, - layerId, - indexPattern, - suggestedPriority: undefined, - field, - }); - const [buckets, metrics] = separateBucketColumns(layer); - const newColumnId = generateId(); - const updatedColumns = { - ...layer.columns, - [newColumnId]: newColumn, - }; - - if (buckets.length === 0 && operation === 'terms') { - (newColumn as TermsIndexPatternColumn).params.size = 5; - } - - const oldDateHistogramIndex = layer.columnOrder.findIndex( - (columnId) => layer.columns[columnId].operationType === 'date_histogram' - ); - const oldDateHistogramId = - oldDateHistogramIndex > -1 ? layer.columnOrder[oldDateHistogramIndex] : null; - - let updatedColumnOrder: string[] = []; - if (oldDateHistogramId) { - if (operation === 'terms') { - // Insert the new terms bucket above the first date histogram - updatedColumnOrder = [ - ...buckets.slice(0, oldDateHistogramIndex), - newColumnId, - ...buckets.slice(oldDateHistogramIndex, buckets.length), - ...metrics, - ]; - } else if (operation === 'date_histogram') { - // Replace date histogram with new date histogram - delete updatedColumns[oldDateHistogramId]; - updatedColumnOrder = layer.columnOrder.map((columnId) => - columnId !== oldDateHistogramId ? columnId : newColumnId - ); - } - } else { - // Insert the new bucket after existing buckets. Users will see the same data - // they already had, with an extra level of detail. - updatedColumnOrder = [...buckets, newColumnId, ...metrics]; - } - return { - indexPatternId: indexPattern.id, - columns: updatedColumns, - columnOrder: updatedColumnOrder, - }; -} - function getEmptyLayerSuggestionsForField( state: IndexPatternPrivateState, layerId: string, @@ -302,9 +262,9 @@ function getEmptyLayerSuggestionsForField( let newLayer: IndexPatternLayer | undefined; const bucketOperation = getBucketOperation(field); if (bucketOperation) { - newLayer = createNewLayerWithBucketAggregation(layerId, indexPattern, field, bucketOperation); + newLayer = createNewLayerWithBucketAggregation(indexPattern, field, bucketOperation); } else if (indexPattern.timeFieldName && getOperationTypesForField(field).length > 0) { - newLayer = createNewLayerWithMetricAggregation(layerId, indexPattern, field); + newLayer = createNewLayerWithMetricAggregation(indexPattern, field); } const newLayerSuggestions = newLayer @@ -324,77 +284,48 @@ function getEmptyLayerSuggestionsForField( } function createNewLayerWithBucketAggregation( - layerId: string, indexPattern: IndexPattern, field: IndexPatternField, operation: OperationType ): IndexPatternLayer { - const countColumn = buildColumn({ + return insertNewColumn({ op: 'count', - columns: {}, - indexPattern, - layerId, - suggestedPriority: undefined, + layer: insertNewColumn({ + op: operation, + layer: { indexPatternId: indexPattern.id, columns: {}, columnOrder: [] }, + columnId: generateId(), + field, + indexPattern, + }), + columnId: generateId(), field: documentField, - }); - - const col1 = generateId(); - const col2 = generateId(); - - // let column know about count column - const column = buildColumn({ - layerId, - op: operation, indexPattern, - columns: { - [col2]: countColumn, - }, - field, - suggestedPriority: undefined, }); - if (operation === 'terms') { - (column as TermsIndexPatternColumn).params.size = 5; - } - - return { - indexPatternId: indexPattern.id, - columns: { - [col1]: column, - [col2]: countColumn, - }, - columnOrder: [col1, col2], - }; } function createNewLayerWithMetricAggregation( - layerId: string, indexPattern: IndexPattern, field: IndexPatternField -): IndexPatternLayer { +): IndexPatternLayer | undefined { const dateField = indexPattern.getFieldByName(indexPattern.timeFieldName!); + const [metricOperation] = getMetricOperationTypes(field); + if (!metricOperation) { + return; + } - const column = getMetricColumn(indexPattern, layerId, field); - - const dateColumn = buildColumn({ + return insertNewColumn({ op: 'date_histogram', - columns: {}, - suggestedPriority: undefined, + layer: insertNewColumn({ + op: metricOperation.type, + layer: { indexPatternId: indexPattern.id, columns: {}, columnOrder: [] }, + columnId: generateId(), + field, + indexPattern, + }), + columnId: generateId(), field: dateField, indexPattern, - layerId, }); - - const col1 = generateId(); - const col2 = generateId(); - - return { - indexPatternId: indexPattern.id, - columns: { - [col1]: dateColumn, - [col2]: column, - }, - columnOrder: [col1, col2], - }; } export function getDatasourceSuggestionsFromCurrentState( @@ -527,57 +458,33 @@ function createChangedNestingSuggestion(state: IndexPatternPrivateState, layerId }); } -function getMetricColumn(indexPattern: IndexPattern, layerId: string, field: IndexPatternField) { - const operationDefinitionsMap = _.keyBy(operationDefinitions, 'type'); - const [column] = getOperationTypesForField(field) - .map((type) => - operationDefinitionsMap[type].buildColumn({ - field, - indexPattern, - layerId, - columns: {}, - suggestedPriority: 0, - }) - ) - .filter((op) => (op.dataType === 'number' || op.dataType === 'document') && !op.isBucketed); - return column; -} - function createMetricSuggestion( indexPattern: IndexPattern, layerId: string, state: IndexPatternPrivateState, field: IndexPatternField ) { - const column = getMetricColumn(indexPattern, layerId, field); + const [operation] = getMetricOperationTypes(field); - if (!column) { + if (!operation) { return; } - const newId = generateId(); - return buildSuggestion({ layerId, state, changeType: 'initial', - updatedLayer: { - indexPatternId: indexPattern.id, - columns: { - [newId]: - column.dataType !== 'document' - ? column - : buildColumn({ - op: 'count', - columns: {}, - indexPattern, - layerId, - suggestedPriority: undefined, - field: documentField, - }), + updatedLayer: insertNewColumn({ + layer: { + indexPatternId: indexPattern.id, + columns: {}, + columnOrder: [], }, - columnOrder: [newId], - }, + columnId: generateId(), + op: operation.type, + field: operation.type === 'count' ? documentField : field, + indexPattern, + }), }); } @@ -591,6 +498,7 @@ function getNestedTitle([outerBucketLabel, innerBucketLabel]: string[]) { }); } +// Replaces all metrics on the table with a different field-based function function createAlternativeMetricSuggestions( indexPattern: IndexPattern, layerId: string, @@ -598,6 +506,7 @@ function createAlternativeMetricSuggestions( ) { const layer = state.layers[layerId]; const suggestions: Array> = []; + layer.columnOrder.forEach((columnId) => { const column = layer.columns[columnId]; if (!hasField(column)) { @@ -607,39 +516,28 @@ function createAlternativeMetricSuggestions( if (!field) { return; } - const alternativeMetricOperations = getOperationTypesForField(field) - .map((op) => - buildColumn({ - op, - columns: layer.columns, - indexPattern, - layerId, - field, - suggestedPriority: undefined, - }) - ) - .filter( - (fullOperation) => - fullOperation.operationType !== column.operationType && !fullOperation.isBucketed - ); - if (alternativeMetricOperations.length === 0) { - return; - } - const newId = generateId(); - const newColumn = alternativeMetricOperations[0]; - const updatedLayer = { - indexPatternId: indexPattern.id, - columns: { [newId]: newColumn }, - columnOrder: [newId], - }; - suggestions.push( - buildSuggestion({ - state, - layerId, - updatedLayer, - changeType: 'initial', - }) + const possibleOperations = getMetricOperationTypes(field).filter( + ({ type }) => type !== column.operationType ); + if (possibleOperations.length) { + const layerWithNewMetric = replaceColumn({ + layer, + indexPattern, + field, + columnId, + op: possibleOperations[0].type, + }); + if (layerWithNewMetric) { + suggestions.push( + buildSuggestion({ + state, + layerId, + updatedLayer: layerWithNewMetric, + changeType: 'initial', + }) + ); + } + } }); return suggestions; } @@ -651,29 +549,17 @@ function createSuggestionWithDefaultDateHistogram( ) { const layer = state.layers[layerId]; const indexPattern = state.indexPatterns[layer.indexPatternId]; - const newId = generateId(); - const [buckets, metrics] = separateBucketColumns(layer); - const timeColumn = buildColumn({ - layerId, - op: 'date_histogram', - indexPattern, - columns: layer.columns, - field: timeField, - suggestedPriority: undefined, - }); - const updatedLayer = { - indexPatternId: layer.indexPatternId, - columns: { - ...layer.columns, - [newId]: timeColumn, - }, - columnOrder: [...buckets, newId, ...metrics], - }; return buildSuggestion({ state, layerId, - updatedLayer, + updatedLayer: insertNewColumn({ + layer, + indexPattern, + field: timeField, + op: 'date_histogram', + columnId: generateId(), + }), label: i18n.translate('xpack.lens.indexpattern.suggestions.overTimeLabel', { defaultMessage: 'Over time', }), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 40eb52fe67c6d..b7df3cc5c3687 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -13,8 +13,6 @@ import { EuiSelectable } from '@elastic/eui'; import { ChangeIndexPattern } from './change_indexpattern'; import { getFieldByNameFactory } from './pure_helpers'; -jest.mock('./state_helpers'); - interface IndexPatternPickerOption { label: string; checked?: 'on' | 'off'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index fac5d7350e45e..64c4122245ce0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -16,7 +16,7 @@ import { IndexPatternField, IndexPatternLayer, } from './types'; -import { updateLayerIndexPattern } from './state_helpers'; +import { updateLayerIndexPattern } from './operations'; import { DateRange, ExistingFields } from '../../common/types'; import { BASE_API_URL } from '../../common'; import { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts index eeb19bba24006..72dfe85dfc0e9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts @@ -4,20 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ -const actual = jest.requireActual('../../operations'); +const actualOperations = jest.requireActual('../operations'); +const actualHelpers = jest.requireActual('../layer_helpers'); -jest.spyOn(actual.operationDefinitionMap.date_histogram, 'paramEditor'); -jest.spyOn(actual.operationDefinitionMap.terms, 'onOtherColumnChanged'); +jest.spyOn(actualOperations.operationDefinitionMap.date_histogram, 'paramEditor'); +jest.spyOn(actualOperations.operationDefinitionMap.terms, 'onOtherColumnChanged'); +jest.spyOn(actualHelpers, 'insertOrReplaceColumn'); +jest.spyOn(actualHelpers, 'insertNewColumn'); +jest.spyOn(actualHelpers, 'replaceColumn'); export const { getAvailableOperationsByMetadata, - buildColumn, getOperations, getOperationDisplay, getOperationTypesForField, getOperationResultType, operationDefinitionMap, operationDefinitions, +} = actualOperations; + +export const { + insertOrReplaceColumn, + insertNewColumn, + replaceColumn, + getColumnOrder, + deleteColumn, + updateColumnParam, + sortByField, + hasField, + updateLayerIndexPattern, + mergeLayer, isColumnTransferable, - changeField, -} = actual; +} = actualHelpers; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index 1cfa63511a45c..bd8c4b4683396 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -52,20 +52,20 @@ export const cardinalityOperation: OperationDefinition { + onFieldChange: (oldColumn, field) => { return { ...oldColumn, label: ofName(field.displayName), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts index 2e95e3fd4250f..bd4b452a49e1d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Operation, DimensionPriority } from '../../../types'; +import { Operation } from '../../../types'; /** * This is the root type of a column. If you are implementing a new @@ -14,7 +14,6 @@ import { Operation, DimensionPriority } from '../../../types'; export interface BaseIndexPatternColumn extends Operation { // Private operationType: string; - suggestedPriority?: DimensionPriority; customLabel?: boolean; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index cdf1a6b760493..e33fc681b2579 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -25,7 +25,7 @@ export const countOperation: OperationDefinition { + onFieldChange: (oldColumn, field) => { return { ...oldColumn, label: field.displayName, @@ -41,20 +41,20 @@ export const countOperation: OperationDefinition { it('should create column object with auto interval for primary time field', () => { const column = dateHistogramOperation.buildColumn({ columns: {}, - suggestedPriority: 0, - layerId: 'first', indexPattern: createMockedIndexPattern(), field: { name: 'timestamp', @@ -207,8 +205,6 @@ describe('date_histogram', () => { it('should create column object with auto interval for non-primary time fields', () => { const column = dateHistogramOperation.buildColumn({ columns: {}, - suggestedPriority: 0, - layerId: 'first', indexPattern: createMockedIndexPattern(), field: { name: 'start_date', @@ -225,8 +221,6 @@ describe('date_histogram', () => { it('should create column object with restrictions', () => { const column = dateHistogramOperation.buildColumn({ columns: {}, - suggestedPriority: 0, - layerId: 'first', indexPattern: createMockedIndexPattern(), field: { name: 'timestamp', @@ -334,7 +328,7 @@ describe('date_histogram', () => { const indexPattern = createMockedIndexPattern(); const newDateField = indexPattern.getFieldByName('start_date')!; - const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField); + const column = dateHistogramOperation.onFieldChange(oldColumn, newDateField); expect(column).toHaveProperty('sourceField', 'start_date'); expect(column).toHaveProperty('params.interval', 'd'); expect(column.label).toContain('start_date'); @@ -354,7 +348,7 @@ describe('date_histogram', () => { const indexPattern = createMockedIndexPattern(); const newDateField = indexPattern.getFieldByName('start_date')!; - const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField); + const column = dateHistogramOperation.onFieldChange(oldColumn, newDateField); expect(column).toHaveProperty('sourceField', 'start_date'); expect(column).toHaveProperty('params.interval', 'auto'); expect(column.label).toContain('start_date'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 19043c03e5a61..659390a42f261 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -19,7 +19,7 @@ import { EuiTextColor, EuiSpacer, } from '@elastic/eui'; -import { updateColumnParam } from '../../state_helpers'; +import { updateColumnParam } from '../layer_helpers'; import { OperationDefinition } from './index'; import { FieldBasedIndexPatternColumn } from './column_types'; import { IndexPatternAggRestrictions, search } from '../../../../../../../src/plugins/data/public'; @@ -59,7 +59,7 @@ export const dateHistogramOperation: OperationDefinition< }; } }, - buildColumn({ suggestedPriority, field }) { + buildColumn({ field }) { let interval = autoInterval; let timeZone: string | undefined; if (field.aggregationRestrictions && field.aggregationRestrictions.date_histogram) { @@ -70,7 +70,6 @@ export const dateHistogramOperation: OperationDefinition< label: field.displayName, dataType: 'date', operationType: 'date_histogram', - suggestedPriority, sourceField: field.name, isBucketed: true, scale: 'interval', @@ -112,7 +111,7 @@ export const dateHistogramOperation: OperationDefinition< return column; }, - onFieldChange: (oldColumn, indexPattern, field) => { + onFieldChange: (oldColumn, field) => { return { ...oldColumn, label: field.displayName, @@ -168,15 +167,7 @@ export const dateHistogramOperation: OperationDefinition< const isCalendarInterval = calendarOnlyIntervals.has(newInterval.unit); const value = `${isCalendarInterval ? '1' : newInterval.value}${newInterval.unit || 'd'}`; - setState( - updateColumnParam({ - state, - layerId, - currentColumn, - value, - paramName: 'interval', - }) - ); + setState(updateColumnParam({ state, layerId, currentColumn, paramName: 'interval', value })); }; return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index 076c1846e35a7..522e951bfba34 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -9,7 +9,7 @@ import React, { MouseEventHandler, useState } from 'react'; import { omit } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiLink, htmlIdGenerator } from '@elastic/eui'; -import { updateColumnParam } from '../../../state_helpers'; +import { updateColumnParam } from '../../layer_helpers'; import { OperationDefinition } from '../index'; import { BaseIndexPatternColumn } from '../column_types'; import { FilterPopover } from './filter_popover'; @@ -75,7 +75,7 @@ export const filtersOperation: OperationDefinition true, - buildColumn({ suggestedPriority, previousColumn }) { + buildColumn({ previousColumn }) { let params = { filters: [defaultFilter] }; if (previousColumn?.operationType === 'terms') { params = { @@ -96,7 +96,6 @@ export const filtersOperation: OperationDefinition { } interface BaseBuildColumnArgs { - suggestedPriority: DimensionPriority | undefined; - layerId: string; columns: Partial>; indexPattern: IndexPattern; } @@ -174,8 +172,7 @@ interface FieldBasedOperationDefinition { buildColumn: ( arg: BaseBuildColumnArgs & { field: IndexPatternField; - // previousColumn?: IndexPatternColumn; - previousColumn?: C; + previousColumn?: IndexPatternColumn; } ) => C; /** @@ -191,15 +188,9 @@ interface FieldBasedOperationDefinition { * index pattern not just a field. * * @param oldColumn The column before the user changed the field. - * @param indexPattern The index pattern that field is on. * @param field The field that the user changed to. */ - onFieldChange: ( - // oldColumn: FieldBasedIndexPatternColumn, - oldColumn: C, - indexPattern: IndexPattern, - field: IndexPatternField - ) => C; + onFieldChange: (oldColumn: C, field: IndexPatternField) => C; } interface OperationDefinitionMap { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index fef575c61475c..37a7ef8ee2563 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -52,18 +52,17 @@ function buildMetricOperation>({ (!newField.aggregationRestrictions || newField.aggregationRestrictions![type]) ); }, - buildColumn: ({ suggestedPriority, field, previousColumn }) => ({ + buildColumn: ({ field, previousColumn }) => ({ label: ofName(field.displayName), dataType: 'number', operationType: type, - suggestedPriority, sourceField: field.name, isBucketed: false, scale: 'ratio', params: previousColumn && previousColumn.dataType === 'number' ? previousColumn.params : undefined, }), - onFieldChange: (oldColumn, indexPattern, field) => { + onFieldChange: (oldColumn, field) => { return { ...oldColumn, label: ofName(field.displayName), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index c6cc6ae13f178..b1cb2312d5bb8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -12,7 +12,8 @@ import { Range } from '../../../../../../../../src/plugins/expressions/common/ex import { RangeEditor } from './range_editor'; import { OperationDefinition } from '../index'; import { FieldBasedIndexPatternColumn } from '../column_types'; -import { updateColumnParam, changeColumn } from '../../../state_helpers'; +import { updateColumnParam } from '../../layer_helpers'; +import { mergeLayer } from '../../../state_helpers'; import { supportedFormats } from '../../../format_column'; import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; import { IndexPattern, IndexPatternField } from '../../../types'; @@ -121,12 +122,11 @@ export const rangeOperation: OperationDefinition { + onFieldChange: (oldColumn, field) => { return { ...oldColumn, label: field.name, @@ -211,23 +211,26 @@ export const rangeOperation: OperationDefinition column && isSortableByColumn(column)) .map(([id]) => id)[0]; + const previousBucketsLength = Object.values(columns).filter((col) => col && col.isBucketed) + .length; + return { label: ofName(field.displayName), dataType: field.type as DataType, operationType: 'terms', scale: 'ordinal', - suggestedPriority, sourceField: field.name, isBucketed: true, params: { - size: DEFAULT_SIZE, + size: previousBucketsLength === 0 ? 5 : DEFAULT_SIZE, orderBy: existingMetricColumn ? { type: 'column', columnId: existingMetricColumn } : { type: 'alphabetical' }, @@ -111,7 +113,7 @@ export const termsOperation: OperationDefinition { + onFieldChange: (oldColumn, field) => { const newParams = { ...oldColumn.params }; if ('format' in newParams && field.type !== 'number') { delete newParams.format; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index bb1b13ba74cc5..0c6d6d18a74dc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -105,7 +105,7 @@ describe('terms', () => { const indexPattern = createMockedIndexPattern(); const newNumberField = indexPattern.getFieldByName('bytes')!; - const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField); + const column = termsOperation.onFieldChange(oldColumn, newNumberField); expect(column).toHaveProperty('dataType', 'number'); expect(column).toHaveProperty('sourceField', 'bytes'); expect(column).toHaveProperty('params.size', 5); @@ -133,7 +133,7 @@ describe('terms', () => { const indexPattern = createMockedIndexPattern(); const newStringField = indexPattern.fields.find((i) => i.name === 'source')!; - const column = termsOperation.onFieldChange(oldColumn, indexPattern, newStringField); + const column = termsOperation.onFieldChange(oldColumn, newStringField); expect(column).toHaveProperty('dataType', 'string'); expect(column).toHaveProperty('sourceField', 'source'); expect(column.params.format).toBeUndefined(); @@ -236,8 +236,6 @@ describe('terms', () => { describe('buildColumn', () => { it('should use type from the passed field', () => { const termsColumn = termsOperation.buildColumn({ - layerId: 'first', - suggestedPriority: undefined, indexPattern: createMockedIndexPattern(), field: { aggregatable: true, @@ -253,8 +251,6 @@ describe('terms', () => { it('should use existing metric column as order column', () => { const termsColumn = termsOperation.buildColumn({ - layerId: 'first', - suggestedPriority: undefined, indexPattern: createMockedIndexPattern(), columns: { col1: { @@ -279,6 +275,36 @@ describe('terms', () => { }) ); }); + + it('should use the default size when there is an existing bucket', () => { + const termsColumn = termsOperation.buildColumn({ + indexPattern: createMockedIndexPattern(), + columns: state.layers.first.columns, + field: { + aggregatable: true, + searchable: true, + type: 'boolean', + name: 'test', + displayName: 'test', + }, + }); + expect(termsColumn.params).toEqual(expect.objectContaining({ size: 3 })); + }); + + it('should use a size of 5 when there are no other buckets', () => { + const termsColumn = termsOperation.buildColumn({ + indexPattern: createMockedIndexPattern(), + columns: {}, + field: { + aggregatable: true, + searchable: true, + type: 'boolean', + name: 'test', + displayName: 'test', + }, + }); + expect(termsColumn.params).toEqual(expect.objectContaining({ size: 5 })); + }); }); describe('onOtherColumnChanged', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts index 31a36c59274da..f0e02c7ff0faf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts @@ -5,4 +5,5 @@ */ export * from './operations'; +export * from './layer_helpers'; export { OperationType, IndexPatternColumn, FieldBasedIndexPatternColumn } from './definitions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts similarity index 52% rename from x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts rename to x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 45008b2d9439a..e1a31dc274837 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -5,22 +5,658 @@ */ import { + insertNewColumn, + replaceColumn, updateColumnParam, - changeColumn, getColumnOrder, deleteColumn, updateLayerIndexPattern, -} from './state_helpers'; -import { operationDefinitionMap } from './operations'; -import { TermsIndexPatternColumn } from './operations/definitions/terms'; -import { DateHistogramIndexPatternColumn } from './operations/definitions/date_histogram'; -import { AvgIndexPatternColumn } from './operations/definitions/metrics'; -import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; -import { getFieldByNameFactory } from './pure_helpers'; +} from './layer_helpers'; +import { operationDefinitionMap, OperationType } from '../operations'; +import { TermsIndexPatternColumn } from './definitions/terms'; +import { DateHistogramIndexPatternColumn } from './definitions/date_histogram'; +import { AvgIndexPatternColumn } from './definitions/metrics'; +import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from '../types'; +import { documentField } from '../document_field'; +import { getFieldByNameFactory } from '../pure_helpers'; -jest.mock('./operations'); +jest.mock('../operations'); + +const indexPatternFields = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + }, + documentField, +]; + +const indexPattern = { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: indexPatternFields, + getFieldByName: getFieldByNameFactory(indexPatternFields), +}; describe('state_helpers', () => { + describe('insertNewColumn', () => { + it('should throw for invalid operations', () => { + expect(() => { + insertNewColumn({ + layer: { indexPatternId: '', columns: {}, columnOrder: [] }, + indexPattern, + op: 'missing' as OperationType, + columnId: 'none', + }); + }).toThrow(); + }); + + it('should update order on inserting a bucketed fieldless operation', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'avg', + sourceField: 'bytes', + }, + }, + }; + expect( + insertNewColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'filters', + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); + }); + + it('should update order on inserting a bucketed field-based operation', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'avg', + sourceField: 'bytes', + }, + }, + }; + expect( + insertNewColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'date_histogram', + field: indexPattern.fields[0], + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); + }); + + it('should insert a metric after buckets', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }; + expect( + insertNewColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'count', + field: documentField, + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2'] })); + }); + + it('should insert new buckets at the end of previous buckets', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1', 'col3'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + col3: { + label: 'Count of records', + dataType: 'document', + isBucketed: false, + + // Private + operationType: 'count', + sourceField: 'Records', + }, + }, + }; + expect( + insertNewColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'filters', + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); + }); + + it('should throw if the aggregation does not support the field', () => { + expect(() => { + insertNewColumn({ + layer: { indexPatternId: '1', columnOrder: [], columns: {} }, + columnId: 'col1', + indexPattern, + op: 'terms', + field: indexPattern.fields[0], + }); + }).toThrow(); + }); + + it('should put the terms agg ahead of the date histogram', () => { + expect( + insertNewColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + columnId: 'col2', + indexPattern, + op: 'terms', + field: indexPattern.fields[2], + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col2', 'col1'] })); + }); + + it('should allow two date histograms', () => { + expect( + insertNewColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + columnId: 'col2', + indexPattern, + op: 'date_histogram', + field: indexPattern.fields[0], + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2'] })); + }); + + it('should allow multiple metrics', () => { + expect( + insertNewColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'avg', + sourceField: 'bytes', + }, + col2: { + label: 'Count of records', + dataType: 'document', + isBucketed: false, + + // Private + operationType: 'count', + sourceField: 'Records', + }, + }, + }, + columnId: 'col3', + indexPattern, + op: 'sum', + field: indexPattern.fields[2], + }) + ).toEqual(expect.objectContaining({ columnOrder: ['col1', 'col2', 'col3'] })); + }); + }); + + describe('replaceColumn', () => { + it('should throw if there is no previous column', () => { + expect(() => { + replaceColumn({ + layer: { indexPatternId: '', columns: {}, columnOrder: [] }, + indexPattern, + op: 'count', + field: documentField, + columnId: 'none', + }); + }).toThrow(); + }); + + it('should throw for invalid operations', () => { + expect(() => { + replaceColumn({ + layer: { indexPatternId: '', columns: {}, columnOrder: [] }, + indexPattern, + op: 'missing' as OperationType, + columnId: 'none', + }); + }).toThrow(); + }); + + it('should update order on changing the column', () => { + const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'avg', + sourceField: 'bytes', + }, + col2: { + label: 'Max of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'max', + sourceField: 'bytes', + }, + }, + }; + expect( + replaceColumn({ + layer, + indexPattern, + columnId: 'col2', + op: 'date_histogram', + field: indexPattern.fields[0], // date + }) + ).toEqual( + expect.objectContaining({ + columnOrder: ['col2', 'col1'], + }) + ); + }); + + it('should throw if nothing is changing', () => { + expect(() => { + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + columnId: 'col1', + indexPattern, + op: 'date_histogram', + field: indexPattern.fields[0], + }); + }).toThrow(); + }); + + it('should throw if switching to a field-based operation without providing a field', () => { + expect(() => { + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + columnId: 'col1', + indexPattern, + op: 'date_histogram', + }); + }).toThrow(); + }); + + it('should carry over params from old column if the switching fields', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + columnId: 'col1', + indexPattern, + op: 'date_histogram', + field: indexPattern.fields[1], + }).columns.col1 + ).toEqual( + expect.objectContaining({ + params: { interval: 'h' }, + }) + ); + }); + + it('should transition from field-based to fieldless operation', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Date histogram of timestamp', + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + indexPattern, + columnId: 'col1', + op: 'filters', + }).columns.col1 + ).toEqual( + expect.objectContaining({ + operationType: 'filters', + }) + ); + }); + + it('should transition from fieldless to field-based operation', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'Filters', + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'filters', + params: { + filters: [], + }, + }, + }, + }, + indexPattern, + columnId: 'col1', + op: 'date_histogram', + field: indexPattern.fields[0], + }).columns.col1 + ).toEqual( + expect.objectContaining({ + operationType: 'date_histogram', + }) + ); + }); + + it('should carry over label on field switch when customLabel flag is set', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'My custom label', + customLabel: true, + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + indexPattern, + columnId: 'col1', + op: 'date_histogram', + field: indexPattern.fields[1], + }).columns.col1 + ).toEqual( + expect.objectContaining({ + label: 'My custom label', + customLabel: true, + }) + ); + }); + + it('should carry over label on operation switch when customLabel flag is set', () => { + expect( + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1'], + columns: { + col1: { + label: 'My custom label', + customLabel: true, + dataType: 'date', + isBucketed: true, + + // Private + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { + interval: 'h', + }, + }, + }, + }, + indexPattern, + columnId: 'col1', + op: 'terms', + field: indexPattern.fields[0], + }).columns.col1 + ).toEqual( + expect.objectContaining({ + label: 'My custom label', + customLabel: true, + }) + ); + }); + + it('should execute adjustments for other columns', () => { + const termsColumn: TermsIndexPatternColumn = { + label: 'Top values of source', + dataType: 'string', + isBucketed: true, + + // Private + operationType: 'terms', + sourceField: 'source', + params: { + orderBy: { type: 'alphabetical' }, + orderDirection: 'asc', + size: 5, + }, + }; + + replaceColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: termsColumn, + col2: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + indexPattern, + columnId: 'col2', + op: 'avg', + field: indexPattern.fields[2], // bytes field + }); + + expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith(termsColumn, { + col1: termsColumn, + col2: expect.objectContaining({ + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + + // Private + operationType: 'avg', + sourceField: 'bytes', + }), + }); + }); + }); + describe('deleteColumn', () => { it('should remove column', () => { const termsColumn: TermsIndexPatternColumn = { @@ -38,14 +674,9 @@ describe('state_helpers', () => { }, }; - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { + expect( + deleteColumn({ + layer: { indexPatternId: '1', columnOrder: ['col1', 'col2'], columns: { @@ -59,11 +690,8 @@ describe('state_helpers', () => { }, }, }, - }, - }; - - expect( - deleteColumn({ state, columnId: 'col2', layerId: 'first' }).layers.first.columns + columnId: 'col2', + }).columns ).toEqual({ col1: { ...termsColumn, @@ -92,34 +720,22 @@ describe('state_helpers', () => { }, }; - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: termsColumn, - col2: { - label: 'Count', - dataType: 'number', - isBucketed: false, - sourceField: 'Records', - operationType: 'count', - }, + deleteColumn({ + layer: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: termsColumn, + col2: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', }, }, }, - }; - - deleteColumn({ - state, columnId: 'col2', - layerId: 'first', }); expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith(termsColumn, { @@ -216,241 +832,6 @@ describe('state_helpers', () => { }); }); - describe('changeColumn', () => { - it('should update order on changing the column', () => { - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - - // Private - operationType: 'avg', - sourceField: 'bytes', - }, - col2: { - label: 'Max of bytes', - dataType: 'number', - isBucketed: false, - - // Private - operationType: 'max', - sourceField: 'bytes', - }, - }, - }, - }, - }; - expect( - changeColumn({ - state, - columnId: 'col2', - layerId: 'first', - newColumn: { - label: 'Date histogram of timestamp', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - params: { - interval: '1d', - }, - sourceField: 'timestamp', - }, - }) - ).toEqual({ - ...state, - layers: { - first: expect.objectContaining({ - columnOrder: ['col2', 'col1'], - }), - }, - }); - }); - - it('should carry over params from old column if the operation type stays the same', () => { - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - label: 'Date histogram of timestamp', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - sourceField: 'timestamp', - params: { - interval: 'h', - }, - }, - }, - }, - }, - }; - expect( - changeColumn({ - state, - layerId: 'first', - columnId: 'col2', - newColumn: { - label: 'Date histogram of order_date', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - sourceField: 'order_date', - params: { - interval: 'w', - }, - }, - }).layers.first.columns.col1 - ).toEqual( - expect.objectContaining({ - params: { interval: 'h' }, - }) - ); - }); - - it('should carry over label if customLabel flag is set', () => { - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - label: 'My custom label', - customLabel: true, - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - sourceField: 'timestamp', - params: { - interval: 'h', - }, - }, - }, - }, - }, - }; - expect( - changeColumn({ - state, - layerId: 'first', - columnId: 'col2', - newColumn: { - label: 'Date histogram of order_date', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - sourceField: 'order_date', - params: { - interval: 'w', - }, - }, - }).layers.first.columns.col1 - ).toEqual( - expect.objectContaining({ - label: 'My custom label', - customLabel: true, - }) - ); - }); - - it('should execute adjustments for other columns', () => { - const termsColumn: TermsIndexPatternColumn = { - label: 'Top values of source', - dataType: 'string', - isBucketed: true, - - // Private - operationType: 'terms', - sourceField: 'source', - params: { - orderBy: { type: 'alphabetical' }, - orderDirection: 'asc', - size: 5, - }, - }; - - const newColumn: AvgIndexPatternColumn = { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - - // Private - operationType: 'avg', - sourceField: 'bytes', - }; - - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - indexPatterns: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1', 'col2'], - columns: { - col1: termsColumn, - col2: { - label: 'Count', - dataType: 'number', - isBucketed: false, - sourceField: 'Records', - operationType: 'count', - }, - }, - }, - }, - }; - - changeColumn({ - state, - layerId: 'first', - columnId: 'col2', - newColumn, - }); - - expect(operationDefinitionMap.terms.onOtherColumnChanged).toHaveBeenCalledWith(termsColumn, { - col1: termsColumn, - col2: newColumn, - }); - }); - }); - describe('getColumnOrder', () => { it('should work for empty columns', () => { expect( @@ -532,57 +913,6 @@ describe('state_helpers', () => { }) ).toEqual(['col1', 'col3', 'col2']); }); - - it('should reorder aggregations based on suggested priority', () => { - expect( - getColumnOrder({ - indexPatternId: '', - columnOrder: [], - columns: { - col1: { - label: 'Top values of category', - dataType: 'string', - isBucketed: true, - - // Private - operationType: 'terms', - sourceField: 'category', - params: { - size: 5, - orderBy: { - type: 'alphabetical', - }, - orderDirection: 'asc', - }, - suggestedPriority: 2, - }, - col2: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - - // Private - operationType: 'avg', - sourceField: 'bytes', - suggestedPriority: 0, - }, - col3: { - label: 'Date histogram of timestamp', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - sourceField: 'timestamp', - suggestedPriority: 1, - params: { - interval: '1d', - }, - }, - }, - }) - ).toEqual(['col3', 'col1', 'col2']); - }); }); describe('updateLayerIndexPattern', () => { @@ -635,7 +965,7 @@ describe('state_helpers', () => { type: 'date', }, ]; - const indexPattern: IndexPattern = { + const newIndexPattern: IndexPattern = { id: 'test', title: '', hasRestrictions: true, @@ -645,7 +975,7 @@ describe('state_helpers', () => { it('should switch index pattern id in layer', () => { const layer = { columnOrder: [], columns: {}, indexPatternId: 'original' }; - expect(updateLayerIndexPattern(layer, indexPattern)).toEqual({ + expect(updateLayerIndexPattern(layer, newIndexPattern)).toEqual({ ...layer, indexPatternId: 'test', }); @@ -677,7 +1007,7 @@ describe('state_helpers', () => { }, indexPatternId: 'original', }; - const updatedLayer = updateLayerIndexPattern(layer, indexPattern); + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); expect(updatedLayer.columnOrder).toEqual(['col1']); expect(updatedLayer.columns).toEqual({ col1: layer.columns.col1, @@ -708,7 +1038,7 @@ describe('state_helpers', () => { }, indexPatternId: 'original', }; - const updatedLayer = updateLayerIndexPattern(layer, indexPattern); + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); expect(updatedLayer.columnOrder).toEqual(['col2']); expect(updatedLayer.columns).toEqual({ col2: layer.columns.col2, @@ -732,7 +1062,7 @@ describe('state_helpers', () => { }, indexPatternId: 'original', }; - const updatedLayer = updateLayerIndexPattern(layer, indexPattern); + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); expect(updatedLayer.columnOrder).toEqual(['col1']); expect(updatedLayer.columns).toEqual({ col1: { @@ -771,7 +1101,7 @@ describe('state_helpers', () => { }, indexPatternId: 'original', }; - const updatedLayer = updateLayerIndexPattern(layer, indexPattern); + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); expect(updatedLayer.columnOrder).toEqual(['col1']); expect(updatedLayer.columns).toEqual({ col1: layer.columns.col1, @@ -804,7 +1134,7 @@ describe('state_helpers', () => { }, indexPatternId: 'original', }; - const updatedLayer = updateLayerIndexPattern(layer, indexPattern); + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); expect(updatedLayer.columnOrder).toEqual(['col1']); expect(updatedLayer.columns).toEqual({ col1: layer.columns.col1, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts new file mode 100644 index 0000000000000..f071df1542147 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -0,0 +1,344 @@ +/* + * 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 _, { partition } from 'lodash'; +import { + operationDefinitionMap, + operationDefinitions, + OperationType, + IndexPatternColumn, +} from './definitions'; +import { + IndexPattern, + IndexPatternField, + IndexPatternLayer, + IndexPatternPrivateState, +} from '../types'; +import { getSortScoreByPriority } from './operations'; +import { mergeLayer } from '../state_helpers'; + +interface ColumnChange { + op: OperationType; + layer: IndexPatternLayer; + columnId: string; + indexPattern: IndexPattern; + field?: IndexPatternField; +} + +export function insertOrReplaceColumn(args: ColumnChange): IndexPatternLayer { + if (args.layer.columns[args.columnId]) { + return replaceColumn(args); + } + return insertNewColumn(args); +} + +export function insertNewColumn({ + op, + layer, + columnId, + field, + indexPattern, +}: ColumnChange): IndexPatternLayer { + const operationDefinition = operationDefinitionMap[op]; + + if (!operationDefinition) { + throw new Error('No suitable operation found for given parameters'); + } + + const baseOptions = { + columns: layer.columns, + indexPattern, + previousColumn: layer.columns[columnId], + }; + + // TODO: Reference based operations require more setup to create the references + + if (operationDefinition.input === 'none') { + const possibleOperation = operationDefinition.getPossibleOperation(); + if (!possibleOperation) { + throw new Error('Tried to create an invalid operation'); + } + const isBucketed = Boolean(possibleOperation.isBucketed); + if (isBucketed) { + return addBucket(layer, operationDefinition.buildColumn(baseOptions), columnId); + } else { + return addMetric(layer, operationDefinition.buildColumn(baseOptions), columnId); + } + } + + if (!field) { + throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`); + } + + const possibleOperation = operationDefinition.getPossibleOperationForField(field); + if (!possibleOperation) { + throw new Error( + `Tried to create an invalid operation ${operationDefinition.type} on ${field.name}` + ); + } + const isBucketed = Boolean(possibleOperation.isBucketed); + if (isBucketed) { + return addBucket(layer, operationDefinition.buildColumn({ ...baseOptions, field }), columnId); + } else { + return addMetric(layer, operationDefinition.buildColumn({ ...baseOptions, field }), columnId); + } +} + +export function replaceColumn({ + layer, + columnId, + indexPattern, + op, + field, +}: ColumnChange): IndexPatternLayer { + const previousColumn = layer.columns[columnId]; + if (!previousColumn) { + throw new Error(`Can't replace column because there is no prior column`); + } + + const isNewOperation = Boolean(op) && op !== previousColumn.operationType; + const operationDefinition = operationDefinitionMap[op || previousColumn.operationType]; + + if (!operationDefinition) { + throw new Error('No suitable operation found for given parameters'); + } + + const baseOptions = { + columns: layer.columns, + indexPattern, + previousColumn, + }; + + if (isNewOperation) { + // TODO: Reference based operations require more setup to create the references + + if (operationDefinition.input === 'none') { + const newColumn = operationDefinition.buildColumn(baseOptions); + + if (previousColumn.customLabel) { + newColumn.customLabel = true; + newColumn.label = previousColumn.label; + } + + return { + ...layer, + columns: adjustColumnReferencesForChangedColumn( + { ...layer.columns, [columnId]: newColumn }, + columnId + ), + }; + } + + if (!field) { + throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`); + } + + const newColumn = operationDefinition.buildColumn({ ...baseOptions, field }); + + if (previousColumn.customLabel) { + newColumn.customLabel = true; + newColumn.label = previousColumn.label; + } + + const newColumns = { ...layer.columns, [columnId]: newColumn }; + return { + ...layer, + columnOrder: getColumnOrder({ ...layer, columns: newColumns }), + columns: adjustColumnReferencesForChangedColumn(newColumns, columnId), + }; + } else if ( + operationDefinition.input === 'field' && + field && + 'sourceField' in previousColumn && + previousColumn.sourceField !== field.name + ) { + // Same operation, new field + const newColumn = operationDefinition.onFieldChange(previousColumn, field); + + if (previousColumn.customLabel) { + newColumn.customLabel = true; + newColumn.label = previousColumn.label; + } + + const newColumns = { ...layer.columns, [columnId]: newColumn }; + return { + ...layer, + columnOrder: getColumnOrder({ ...layer, columns: newColumns }), + columns: adjustColumnReferencesForChangedColumn(newColumns, columnId), + }; + } else { + throw new Error('nothing changed'); + } +} + +function addBucket( + layer: IndexPatternLayer, + column: IndexPatternColumn, + addedColumnId: string +): IndexPatternLayer { + const [buckets, metrics] = separateBucketColumns(layer); + + const oldDateHistogramIndex = layer.columnOrder.findIndex( + (columnId) => layer.columns[columnId].operationType === 'date_histogram' + ); + + let updatedColumnOrder: string[] = []; + if (oldDateHistogramIndex > -1 && column.operationType === 'terms') { + // Insert the new terms bucket above the first date histogram + updatedColumnOrder = [ + ...buckets.slice(0, oldDateHistogramIndex), + addedColumnId, + ...buckets.slice(oldDateHistogramIndex, buckets.length), + ...metrics, + ]; + } else { + // Insert the new bucket after existing buckets. Users will see the same data + // they already had, with an extra level of detail. + updatedColumnOrder = [...buckets, addedColumnId, ...metrics]; + } + return { + ...layer, + columns: { ...layer.columns, [addedColumnId]: column }, + columnOrder: updatedColumnOrder, + }; +} + +function addMetric( + layer: IndexPatternLayer, + column: IndexPatternColumn, + addedColumnId: string +): IndexPatternLayer { + return { + ...layer, + columns: { + ...layer.columns, + [addedColumnId]: column, + }, + columnOrder: [...layer.columnOrder, addedColumnId], + }; +} + +function separateBucketColumns(layer: IndexPatternLayer) { + return partition(layer.columnOrder, (columnId) => layer.columns[columnId]?.isBucketed); +} + +export function getMetricOperationTypes(field: IndexPatternField) { + return operationDefinitions.sort(getSortScoreByPriority).filter((definition) => { + if (definition.input !== 'field') return; + const metadata = definition.getPossibleOperationForField(field); + return metadata && !metadata.isBucketed && metadata.dataType === 'number'; + }); +} + +export function updateColumnParam({ + state, + layerId, + currentColumn, + paramName, + value, +}: { + state: IndexPatternPrivateState; + layerId: string; + currentColumn: C; + paramName: string; + value: unknown; +}): IndexPatternPrivateState { + const columnId = Object.entries(state.layers[layerId].columns).find( + ([_columnId, column]) => column === currentColumn + )![0]; + + const layer = state.layers[layerId]; + + return mergeLayer({ + state, + layerId, + newLayer: { + columns: { + ...layer.columns, + [columnId]: { + ...currentColumn, + params: { + ...currentColumn.params, + [paramName]: value, + }, + }, + }, + }, + }); +} + +function adjustColumnReferencesForChangedColumn( + columns: Record, + columnId: string +) { + const newColumns = { ...columns }; + Object.keys(newColumns).forEach((currentColumnId) => { + if (currentColumnId !== columnId) { + const currentColumn = newColumns[currentColumnId]; + const operationDefinition = operationDefinitionMap[currentColumn.operationType]; + newColumns[currentColumnId] = operationDefinition.onOtherColumnChanged + ? operationDefinition.onOtherColumnChanged(currentColumn, newColumns) + : currentColumn; + } + }); + return newColumns; +} + +export function deleteColumn({ + layer, + columnId, +}: { + layer: IndexPatternLayer; + columnId: string; +}): IndexPatternLayer { + const hypotheticalColumns = { ...layer.columns }; + delete hypotheticalColumns[columnId]; + + const newLayer = { + ...layer, + columns: adjustColumnReferencesForChangedColumn(hypotheticalColumns, columnId), + }; + return { ...newLayer, columnOrder: getColumnOrder(newLayer) }; +} + +export function getColumnOrder(layer: IndexPatternLayer): string[] { + const [aggregations, metrics] = _.partition( + Object.entries(layer.columns), + ([id, col]) => col.isBucketed + ); + + return aggregations.map(([id]) => id).concat(metrics.map(([id]) => id)); +} + +/** + * Returns true if the given column can be applied to the given index pattern + */ +export function isColumnTransferable(column: IndexPatternColumn, newIndexPattern: IndexPattern) { + return operationDefinitionMap[column.operationType].isTransferable(column, newIndexPattern); +} + +export function updateLayerIndexPattern( + layer: IndexPatternLayer, + newIndexPattern: IndexPattern +): IndexPatternLayer { + const keptColumns: IndexPatternLayer['columns'] = _.pickBy(layer.columns, (column) => + isColumnTransferable(column, newIndexPattern) + ); + const newColumns: IndexPatternLayer['columns'] = _.mapValues(keptColumns, (column) => { + const operationDefinition = operationDefinitionMap[column.operationType]; + return operationDefinition.transfer + ? operationDefinition.transfer(column, newIndexPattern) + : column; + }); + const newColumnOrder = layer.columnOrder.filter((columnId) => newColumns[columnId]); + + return { + ...layer, + indexPatternId: newIndexPattern.id, + columns: newColumns, + columnOrder: newColumnOrder, + }; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 9767d4bdca688..99149c3e74568 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColumn } from './index'; -import { AvgIndexPatternColumn } from './definitions/metrics'; -import { IndexPatternPrivateState } from '../types'; -import { documentField } from '../document_field'; +import { getOperationTypesForField, getAvailableOperationsByMetadata } from './index'; import { getFieldByNameFactory } from '../pure_helpers'; jest.mock('../loader'); @@ -157,73 +154,6 @@ describe('getOperationTypesForField', () => { }); }); - describe('buildColumn', () => { - const state: IndexPatternPrivateState = { - indexPatternRefs: [], - existingFields: {}, - currentIndexPatternId: '1', - isFirstExistenceFetch: false, - indexPatterns: expectedIndexPatterns, - layers: { - first: { - indexPatternId: '1', - columnOrder: ['col1'], - columns: { - col1: { - label: 'Date histogram of timestamp', - dataType: 'date', - isBucketed: true, - - // Private - operationType: 'date_histogram', - params: { - interval: '1d', - }, - sourceField: 'timestamp', - }, - }, - }, - }, - }; - - it('should build a column for the given field-based operation type if it is passed in', () => { - const column = buildColumn({ - layerId: 'first', - indexPattern: expectedIndexPatterns[1], - columns: state.layers.first.columns, - suggestedPriority: 0, - op: 'count', - field: documentField, - }); - expect(column.operationType).toEqual('count'); - }); - - it('should build a column for the given no-input operation type if it is passed in', () => { - const column = buildColumn({ - layerId: 'first', - indexPattern: expectedIndexPatterns[1], - columns: state.layers.first.columns, - suggestedPriority: 0, - op: 'filters', - }); - expect(column.operationType).toEqual('filters'); - }); - - it('should build a column for the given operation type and field if it is passed in', () => { - const field = expectedIndexPatterns[1].fields[1]; - const column = buildColumn({ - layerId: 'first', - indexPattern: expectedIndexPatterns[1], - columns: state.layers.first.columns, - suggestedPriority: 0, - op: 'avg', - field, - }) as AvgIndexPatternColumn; - expect(column.operationType).toEqual('avg'); - expect(column.sourceField).toEqual(field.name); - }); - }); - describe('getAvailableOperationsByMetaData', () => { it('should put the average operation first', () => { const numberOperation = getAvailableOperationsByMetadata(expectedIndexPatterns[1]).find( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index 46dd73ba849a2..8d489df366088 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DimensionPriority, OperationMetadata } from '../../types'; +import _ from 'lodash'; +import { OperationMetadata } from '../../types'; import { operationDefinitionMap, operationDefinitions, GenericOperationDefinition, OperationType, - IndexPatternColumn, } from './definitions'; import { IndexPattern, IndexPatternField } from '../types'; import { documentField } from '../document_field'; +export { operationDefinitionMap } from './definitions'; + /** * Returns all available operation types as a list at runtime. * This will be an array of each member of the union type `OperationType` @@ -24,13 +26,6 @@ export function getOperations(): OperationType[] { return Object.keys(operationDefinitionMap) as OperationType[]; } -/** - * Returns true if the given column can be applied to the given index pattern - */ -export function isColumnTransferable(column: IndexPatternColumn, newIndexPattern: IndexPattern) { - return operationDefinitionMap[column.operationType].isTransferable(column, newIndexPattern); -} - /** * Returns a list of the display names of all operations with any guaranteed order. */ @@ -51,7 +46,10 @@ export function getOperationDisplay() { return display; } -function getSortScoreByPriority(a: GenericOperationDefinition, b: GenericOperationDefinition) { +export function getSortScoreByPriority( + a: GenericOperationDefinition, + b: GenericOperationDefinition +) { return (b.priority || Number.NEGATIVE_INFINITY) - (a.priority || Number.NEGATIVE_INFINITY); } @@ -169,82 +167,3 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) { return Object.values(operationByMetadata); } - -/** - * Changes the field of the passed in colum. To do so, this method uses the `onFieldChange` function of - * the operation definition of the column. Returns a new column object with the field changed. - * @param column The column object with the old field configured - * @param indexPattern The index pattern associated to the layer of the column - * @param newField The new field the column should be switched to - */ -export function changeField( - column: IndexPatternColumn, - indexPattern: IndexPattern, - newField: IndexPatternField -) { - const operationDefinition = operationDefinitionMap[column.operationType]; - - if (operationDefinition.input === 'field' && 'sourceField' in column) { - return operationDefinition.onFieldChange(column, indexPattern, newField); - } else { - throw new Error( - "Invariant error: Cannot change field if operation isn't a field based operaiton" - ); - } -} - -/** - * Builds a column object based on the context passed in. It tries - * to find the applicable operation definition and then calls the `buildColumn` - * function of that definition. It passes in the given `field` (if available), - * `suggestedPriority`, `layerId` and the currently existing `columns`. - * * If `op` is specified, the specified operation definition is used directly. - * * If `asDocumentOperation` is true, the first matching document-operation is used. - * * If `field` is specified, the first matching field based operation applicable to the field is used. - */ -export function buildColumn({ - op, - columns, - field, - layerId, - indexPattern, - suggestedPriority, - previousColumn, -}: { - op: OperationType; - columns: Partial>; - suggestedPriority: DimensionPriority | undefined; - layerId: string; - indexPattern: IndexPattern; - field?: IndexPatternField; - previousColumn?: IndexPatternColumn; -}): IndexPatternColumn { - const operationDefinition = operationDefinitionMap[op]; - - if (!operationDefinition) { - throw new Error('No suitable operation found for given parameters'); - } - - const baseOptions = { - columns, - suggestedPriority, - layerId, - indexPattern, - previousColumn, - }; - - if (operationDefinition.input === 'none') { - return operationDefinition.buildColumn(baseOptions); - } - - if (!field) { - throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`); - } - - return operationDefinition.buildColumn({ - ...baseOptions, - field, - }); -} - -export { operationDefinitionMap } from './definitions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts index 2e92d4ad8f88b..32e45965f3e75 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/state_helpers.ts @@ -4,162 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import { isColumnTransferable, operationDefinitionMap, IndexPatternColumn } from './operations'; -import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; - -export function updateColumnParam({ - state, - layerId, - currentColumn, - paramName, - value, -}: { - state: IndexPatternPrivateState; - layerId: string; - currentColumn: C; - paramName: string; - value: unknown; -}): IndexPatternPrivateState { - const columnId = Object.entries(state.layers[layerId].columns).find( - ([_columnId, column]) => column === currentColumn - )![0]; - - const layer = state.layers[layerId]; - - return mergeLayer({ - state, - layerId, - newLayer: { - columns: { - ...layer.columns, - [columnId]: { - ...currentColumn, - params: { - ...currentColumn.params, - [paramName]: value, - }, - }, - }, - }, - }); -} - -function adjustColumnReferencesForChangedColumn( - columns: Record, - columnId: string -) { - const newColumns = { ...columns }; - Object.keys(newColumns).forEach((currentColumnId) => { - if (currentColumnId !== columnId) { - const currentColumn = newColumns[currentColumnId]; - const operationDefinition = operationDefinitionMap[currentColumn.operationType]; - newColumns[currentColumnId] = operationDefinition.onOtherColumnChanged - ? operationDefinition.onOtherColumnChanged(currentColumn, newColumns) - : currentColumn; - } - }); - return newColumns; -} - -export function changeColumn({ - state, - layerId, - columnId, - newColumn, - keepParams = true, -}: { - state: IndexPatternPrivateState; - layerId: string; - columnId: string; - newColumn: C; - keepParams?: boolean; -}): IndexPatternPrivateState { - const oldColumn = state.layers[layerId].columns[columnId]; - - const updatedColumn = - keepParams && - oldColumn && - oldColumn.operationType === newColumn.operationType && - 'params' in oldColumn - ? { ...newColumn, params: oldColumn.params } - : newColumn; - - if (oldColumn && oldColumn.customLabel) { - updatedColumn.customLabel = true; - updatedColumn.label = oldColumn.label; - } - - const layer = { - ...state.layers[layerId], - }; - - const newColumns = adjustColumnReferencesForChangedColumn( - { - ...layer.columns, - [columnId]: updatedColumn, - }, - columnId - ); - - return mergeLayer({ - state, - layerId, - newLayer: { - columnOrder: getColumnOrder({ - ...layer, - columns: newColumns, - }), - columns: newColumns, - }, - }); -} - -export function deleteColumn({ - state, - layerId, - columnId, -}: { - state: IndexPatternPrivateState; - layerId: string; - columnId: string; -}): IndexPatternPrivateState { - const hypotheticalColumns = { ...state.layers[layerId].columns }; - delete hypotheticalColumns[columnId]; - - const newColumns = adjustColumnReferencesForChangedColumn(hypotheticalColumns, columnId); - const layer = { - ...state.layers[layerId], - columns: newColumns, - }; - - return mergeLayer({ - state, - layerId, - newLayer: { - ...layer, - columnOrder: getColumnOrder(layer), - }, - }); -} - -export function getColumnOrder(layer: IndexPatternLayer): string[] { - const [aggregations, metrics] = _.partition( - Object.entries(layer.columns), - ([id, col]) => col.isBucketed - ); - - return aggregations - .sort(([id, col], [id2, col2]) => { - return ( - // Sort undefined orders last - (col.suggestedPriority !== undefined ? col.suggestedPriority : Number.MAX_SAFE_INTEGER) - - (col2.suggestedPriority !== undefined ? col2.suggestedPriority : Number.MAX_SAFE_INTEGER) - ); - }) - .map(([id]) => id) - .concat(metrics.map(([id]) => id)); -} +import { IndexPatternPrivateState, IndexPatternLayer } from './types'; export function mergeLayer({ state, @@ -178,26 +23,3 @@ export function mergeLayer({ }, }; } - -export function updateLayerIndexPattern( - layer: IndexPatternLayer, - newIndexPattern: IndexPattern -): IndexPatternLayer { - const keptColumns: IndexPatternLayer['columns'] = _.pickBy(layer.columns, (column) => - isColumnTransferable(column, newIndexPattern) - ); - const newColumns: IndexPatternLayer['columns'] = _.mapValues(keptColumns, (column) => { - const operationDefinition = operationDefinitionMap[column.operationType]; - return operationDefinition.transfer - ? operationDefinition.transfer(column, newIndexPattern) - : column; - }); - const newColumnOrder = layer.columnOrder.filter((columnId) => newColumns[columnId]); - - return { - ...layer, - indexPatternId: newIndexPattern.id, - columns: newColumns, - columnOrder: newColumnOrder, - }; -} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 4ad849c5d441e..b8bceb5454bc8 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -72,9 +72,6 @@ export interface EditorFrameStart { createInstance: () => Promise; } -// Hints the default nesting to the data source. 0 is the highest priority -export type DimensionPriority = 0 | 1 | 2; - export interface TableSuggestionColumn { columnId: string; operation: Operation; @@ -220,11 +217,6 @@ interface SharedDimensionProps { */ filterOperations: (operation: OperationMetadata) => boolean; - /** Visualizations can hint at the role this dimension would play, which - * affects the default ordering of the query - */ - suggestedPriority?: DimensionPriority; - /** Some dimension editors will allow users to change the operation grouping * from the panel, and this lets the visualization hint that it doesn't want * users to have that level of control diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 7e155de14a39a..1f135929dac21 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -178,7 +178,6 @@ export const getXyVisualization = ({ groupLabel: getAxisName('x', { isHorizontal }), accessors: layer.xAccessor ? [layer.xAccessor] : [], filterOperations: isBucketed, - suggestedPriority: 1, supportsMoreColumns: !layer.xAccessor, dataTestSubj: 'lnsXY_xDimensionPanel', }, @@ -199,7 +198,6 @@ export const getXyVisualization = ({ }), accessors: layer.splitAccessor ? [layer.splitAccessor] : [], filterOperations: isBucketed, - suggestedPriority: 0, supportsMoreColumns: !layer.splitAccessor, dataTestSubj: 'lnsXY_splitDimensionPanel', required: layer.seriesType.includes('percentage'), diff --git a/x-pack/plugins/lens/server/migrations.test.ts b/x-pack/plugins/lens/server/migrations.test.ts index 676494dcab619..957da5cbb3743 100644 --- a/x-pack/plugins/lens/server/migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { migrations } from './migrations'; -import { SavedObjectMigrationContext } from 'src/core/server'; +import { migrations, LensDocShape } from './migrations'; +import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'src/core/server'; describe('Lens migrations', () => { describe('7.7.0 missing dimensions in XY', () => { @@ -507,4 +507,88 @@ describe('Lens migrations', () => { expect(result).toMatchSnapshot(); }); }); + + describe('7.11.0 remove suggested priority', () => { + const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + + const example = { + type: 'lens', + attributes: { + state: { + datasourceStates: { + indexpattern: { + currentIndexPatternId: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + layers: { + 'bd09dc71-a7e2-42d0-83bd-85df8291f03c': { + indexPatternId: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + columns: { + '1d9cc16c-1460-41de-88f8-471932ecbc97': { + label: 'products.created_on', + dataType: 'date', + operationType: 'date_histogram', + sourceField: 'products.created_on', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + suggestedPriority: 0, + }, + '66115819-8481-4917-a6dc-8ffb10dd02df': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + suggestedPriority: 1, + isBucketed: false, + scale: 'ratio', + sourceField: 'Records', + }, + }, + columnOrder: [ + '1d9cc16c-1460-41de-88f8-471932ecbc97', + '66115819-8481-4917-a6dc-8ffb10dd02df', + ], + }, + }, + }, + }, + datasourceMetaData: { + filterableIndexPatterns: [ + { id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', title: 'kibana_sample_data_ecommerce' }, + ], + }, + visualization: { + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: 'bd09dc71-a7e2-42d0-83bd-85df8291f03c', + accessors: ['66115819-8481-4917-a6dc-8ffb10dd02df'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + xAccessor: '1d9cc16c-1460-41de-88f8-471932ecbc97', + }, + ], + }, + query: { query: '', language: 'kuery' }, + filters: [], + }, + title: 'Bar chart', + visualizationType: 'lnsXY', + }, + }; + + it('should remove the suggested priority from all columns', () => { + const result = migrations['7.11.0'](example, context) as ReturnType< + SavedObjectMigrationFn + >; + const resultLayers = result.attributes.state.datasourceStates.indexpattern.layers; + const layersWithSuggestedPriority = Object.values(resultLayers).reduce( + (count, layer) => + count + Object.values(layer.columns).filter((col) => 'suggestedPriority' in col).length, + 0 + ); + + expect(layersWithSuggestedPriority).toEqual(0); + }); + }); }); diff --git a/x-pack/plugins/lens/server/migrations.ts b/x-pack/plugins/lens/server/migrations.ts index fdbfa1e455f60..3ec3abdfd6260 100644 --- a/x-pack/plugins/lens/server/migrations.ts +++ b/x-pack/plugins/lens/server/migrations.ts @@ -31,7 +31,7 @@ interface LensDocShapePre710 { string, { columnOrder: string[]; - columns: Record; + columns: Record>; indexPatternId: string; } >; @@ -43,7 +43,7 @@ interface LensDocShapePre710 { }; } -interface LensDocShape { +export interface LensDocShape { id?: string; type?: string; visualizationType: string | null; @@ -56,7 +56,7 @@ interface LensDocShape { string, { columnOrder: string[]; - columns: Record; + columns: Record>; } >; }; @@ -310,10 +310,34 @@ const extractReferences: SavedObjectMigrationFn = (doc) => { + const newDoc = cloneDeep(doc); + const datasourceLayers = newDoc.attributes.state.datasourceStates.indexpattern.layers || {}; + newDoc.attributes.state.datasourceStates.indexpattern.layers = Object.fromEntries( + Object.entries(datasourceLayers).map(([layerId, layer]) => { + return [ + layerId, + { + ...layer, + columns: Object.fromEntries( + Object.entries(layer.columns).map(([columnId, column]) => { + const copy = { ...column }; + delete copy.suggestedPriority; + return [columnId, copy]; + }) + ), + }, + ]; + }) + ); + return newDoc; +}; + export const migrations: SavedObjectMigrationMap = { '7.7.0': removeInvalidAccessors, // The order of these migrations matter, since the timefield migration relies on the aggConfigs // sitting directly on the esaggs as an argument and not a nested function (which lens_auto_date was). '7.8.0': (doc, context) => addTimeFieldToEsaggs(removeLensAutoDate(doc, context), context), '7.10.0': extractReferences, + '7.11.0': removeSuggestedPriority, }; From 8842e9b7a96640ea626f90ea7201233eafb217e5 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 10 Nov 2020 20:50:09 +0200 Subject: [PATCH 63/87] [Autocomplete] Support useTimeFilter option (#81515) * pass timefilter to autocomplete * ignoreTimeRange advanced setting * Show all results in filter bar autocomplete * Round timerange to minute for autocomplete memoization * Change useTimeFilter param name Update autocomplete tests and docs * Fix maps test useTimeFilter in uptime * docs * useTimeRange in apm * remove date validation * Update src/plugins/data/common/constants.ts Co-authored-by: Lukas Olson * docs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Lukas Olson --- ...ns-data-public.querysuggestiongetfnargs.md | 1 + ...c.querysuggestiongetfnargs.usetimerange.md | 11 +++++ ...-plugin-plugins-data-public.ui_settings.md | 1 + ...-plugin-plugins-data-server.ui_settings.md | 1 + src/plugins/data/common/constants.ts | 1 + .../providers/query_suggestion_provider.ts | 1 + .../value_suggestion_provider.test.ts | 49 +++++++++++++++++++ .../providers/value_suggestion_provider.ts | 34 ++++++++++--- src/plugins/data/public/public.api.md | 3 ++ .../public/query/timefilter/timefilter.ts | 11 +++-- .../filter_editor/phrase_suggestor.tsx | 2 + .../autocomplete/value_suggestions_route.ts | 10 ++-- src/plugins/data/server/server.api.md | 1 + src/plugins/data/server/ui_settings.ts | 12 +++++ .../shared/KueryBar/get_bool_filter.ts | 18 +------ .../components/shared/KueryBar/index.tsx | 1 + .../providers/kql_query_suggestion/value.ts | 3 +- .../overview/kuery_bar/kuery_bar.tsx | 13 +---- .../apps/discover/value_suggestions.ts | 3 +- .../functional/apps/maps/vector_styling.js | 6 ++- 20 files changed, 136 insertions(+), 46 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md index 96e43ca836891..de6f4563b678a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md @@ -23,4 +23,5 @@ export interface QuerySuggestionGetFnArgs | [selectionEnd](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionend.md) | number | | | [selectionStart](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.selectionstart.md) | number | | | [signal](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.signal.md) | AbortSignal | | +| [useTimeRange](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md new file mode 100644 index 0000000000000..a29cddd81d885 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) > [useTimeRange](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.usetimerange.md) + +## QuerySuggestionGetFnArgs.useTimeRange property + +Signature: + +```typescript +useTimeRange?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md index 6ed20beb396f1..9a0c37c8edd38 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -37,5 +37,6 @@ UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md index 2d4ce75b956df..c2edc64f292d2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -37,5 +37,6 @@ UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; } ``` diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 43120583bd3a4..a70c5eedbf847 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -49,4 +49,5 @@ export const UI_SETTINGS = { INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder', FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault', FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues', + AUTOCOMPLETE_USE_TIMERANGE: 'autocomplete:useTimeRange', } as const; diff --git a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 16500ac9e239a..da523e740c3d6 100644 --- a/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -39,6 +39,7 @@ export interface QuerySuggestionGetFnArgs { selectionStart: number; selectionEnd: number; signal?: AbortSignal; + useTimeRange?: boolean; boolFilter?: any; } diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 6b0c0f07cf6c9..0ef5b7db958e4 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -21,6 +21,26 @@ import { stubIndexPattern, stubFields } from '../../stubs'; import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; import { IUiSettingsClient, CoreSetup } from 'kibana/public'; +jest.mock('../../services', () => ({ + getQueryService: () => ({ + timefilter: { + timefilter: { + createFilter: () => { + return { + time: 'fake', + }; + }, + getTime: () => { + return { + to: 'now', + from: 'now-15m', + }; + }, + }, + }, + }), +})); + describe('FieldSuggestions', () => { let getValueSuggestions: ValueSuggestionsGetFn; let http: any; @@ -94,6 +114,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }); expect(http.fetch).toHaveBeenCalled(); @@ -107,6 +128,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }; await getValueSuggestions(args); @@ -123,6 +145,7 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field, query: '', + useTimeRange: false, }; const { now } = Date; @@ -146,50 +169,76 @@ describe('FieldSuggestions', () => { indexPattern: stubIndexPattern, field: fields[0], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[0], query: 'query', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[1], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: stubIndexPattern, field: fields[1], query: 'query', + useTimeRange: false, }); const customIndexPattern = { ...stubIndexPattern, title: 'customIndexPattern', + useTimeRange: false, }; await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[0], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[0], query: 'query', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[1], query: '', + useTimeRange: false, }); await getValueSuggestions({ indexPattern: customIndexPattern, field: fields[1], query: 'query', + useTimeRange: false, }); expect(http.fetch).toHaveBeenCalledTimes(8); }); + + it('should apply timefilter', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body).filters).toHaveLength(1); + expect(http.fetch).toHaveBeenCalled(); + }); }); }); diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index a6a45a26f06b3..fe9f939a0261d 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -17,15 +17,16 @@ * under the License. */ +import dateMath from '@elastic/datemath'; import { memoize } from 'lodash'; import { CoreSetup } from 'src/core/public'; -import { IIndexPattern, IFieldType, UI_SETTINGS } from '../../../common'; +import { IIndexPattern, IFieldType, UI_SETTINGS, buildQueryFromFilters } from '../../../common'; +import { getQueryService } from '../../services'; -function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { +function resolver(title: string, field: IFieldType, query: string, filters: any[]) { // Only cache results for a minute const ttl = Math.floor(Date.now() / 1000 / 60); - - return [ttl, query, title, field.name, JSON.stringify(boolFilter)].join('|'); + return [ttl, query, title, field.name, JSON.stringify(filters)].join('|'); } export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; @@ -34,18 +35,31 @@ interface ValueSuggestionsGetFnArgs { indexPattern: IIndexPattern; field: IFieldType; query: string; + useTimeRange?: boolean; boolFilter?: any[]; signal?: AbortSignal; } +const getAutocompleteTimefilter = (indexPattern: IIndexPattern) => { + const { timefilter } = getQueryService().timefilter; + const timeRange = timefilter.getTime(); + + // Use a rounded timerange so that memoizing works properly + const roundedTimerange = { + from: dateMath.parse(timeRange.from)!.startOf('minute').toISOString(), + to: dateMath.parse(timeRange.to)!.endOf('minute').toISOString(), + }; + return timefilter.createFilter(indexPattern, roundedTimerange); +}; + export const getEmptyValueSuggestions = (() => Promise.resolve([])) as ValueSuggestionsGetFn; export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( - (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => + (index: string, field: IFieldType, query: string, filters: any = [], signal?: AbortSignal) => core.http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', - body: JSON.stringify({ query, field: field.name, boolFilter }), + body: JSON.stringify({ query, field: field.name, filters }), signal, }), resolver @@ -55,12 +69,15 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG indexPattern, field, query, + useTimeRange, boolFilter, signal, }: ValueSuggestionsGetFnArgs): Promise => { const shouldSuggestValues = core!.uiSettings.get( UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES ); + useTimeRange = + useTimeRange ?? core!.uiSettings.get(UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE); const { title } = indexPattern; if (field.type === 'boolean') { @@ -69,6 +86,9 @@ export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsG return []; } - return await requestSuggestions(title, field, query, boolFilter, signal); + const timeFilter = useTimeRange ? getAutocompleteTimefilter(indexPattern) : undefined; + const filterQuery = timeFilter ? buildQueryFromFilters([timeFilter], indexPattern).filter : []; + const filters = [...(boolFilter ? boolFilter : []), ...filterQuery]; + return await requestSuggestions(title, field, query, filters, signal); }; }; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 31b05bd4763a2..643bc46465360 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1840,6 +1840,8 @@ export interface QuerySuggestionGetFnArgs { selectionStart: number; // (undocumented) signal?: AbortSignal; + // (undocumented) + useTimeRange?: boolean; } // Warning: (ae-missing-release-tag) "QuerySuggestionTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2309,6 +2311,7 @@ export const UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; }; diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 49a8c68f6916f..7278ceaaddcce 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -24,9 +24,14 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { getForceNow } from './lib/get_force_now'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; -import { calculateBounds, getTime, RefreshInterval, TimeRange } from '../../../common'; +import { + calculateBounds, + getTime, + IIndexPattern, + RefreshInterval, + TimeRange, +} from '../../../common'; import { TimeHistoryContract } from './time_history'; -import { IndexPattern } from '../../index_patterns'; // TODO: remove! @@ -170,7 +175,7 @@ export class Timefilter { } }; - public createFilter = (indexPattern: IndexPattern, timeRange?: TimeRange) => { + public createFilter = (indexPattern: IIndexPattern, timeRange?: TimeRange) => { return getTime(indexPattern, timeRange ? timeRange : this._time, { forceNow: this.getForceNow(), }); diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 719827a98cc63..c420734a43d41 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -85,6 +85,8 @@ export class PhraseSuggestorUI extends React.Com field, query, signal: this.abortController.signal, + // Show all results in filter bar autocomplete + useTimeRange: false, }); this.setState({ suggestions, isLoading: false }); diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 6a5b7d1d5b414..89ee0995f4140 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -45,7 +45,7 @@ export function registerValueSuggestionsRoute( { field: schema.string(), query: schema.string(), - boolFilter: schema.maybe(schema.any()), + filters: schema.maybe(schema.any()), }, { unknowns: 'allow' } ), @@ -53,7 +53,7 @@ export function registerValueSuggestionsRoute( }, async (context, request, response) => { const config = await config$.pipe(first()).toPromise(); - const { field: fieldName, query, boolFilter } = request.body; + const { field: fieldName, query, filters } = request.body; const { index } = request.params; const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); @@ -66,7 +66,7 @@ export function registerValueSuggestionsRoute( const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); const field = indexPattern && getFieldByName(fieldName, indexPattern); - const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); + const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); try { const result = await client.callAsCurrentUser('search', { index, body }, { signal }); @@ -88,7 +88,7 @@ async function getBody( { timeout, terminate_after }: Record, field: IFieldType | string, query: string, - boolFilter: Filter[] = [] + filters: Filter[] = [] ) { const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name); @@ -108,7 +108,7 @@ async function getBody( terminate_after, query: { bool: { - filter: boolFilter, + filter: filters, }, }, aggs: { diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 131b3e4c39c6b..2984ca336819a 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1181,6 +1181,7 @@ export const UI_SETTINGS: { readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; + readonly AUTOCOMPLETE_USE_TIMERANGE: "autocomplete:useTimeRange"; }; // Warning: (ae-missing-release-tag) "usageProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 763a086d7688d..9393700a0e771 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -684,5 +684,17 @@ export function getUiSettings(): Record> { }), schema: schema.boolean(), }, + [UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE]: { + name: i18n.translate('data.advancedSettings.autocompleteIgnoreTimerange', { + defaultMessage: 'Use time range', + description: 'Restrict autocomplete results to the current time range', + }), + value: true, + description: i18n.translate('data.advancedSettings.autocompleteIgnoreTimerangeText', { + defaultMessage: + 'Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range.', + }), + schema: schema.boolean(), + }, }; } diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index 74d7ace20dae0..f0d12fd16bf7a 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -26,23 +26,7 @@ export function getBoolFilter({ serviceName?: string; urlParams: IUrlParams; }) { - const { start, end } = urlParams; - - if (!start || !end) { - throw new Error('Date range was not defined'); - } - - const boolFilter: ESFilter[] = [ - { - range: { - '@timestamp': { - gte: new Date(start).getTime(), - lte: new Date(end).getTime(), - format: 'epoch_millis', - }, - }, - }, - ]; + const boolFilter: ESFilter[] = []; if (serviceName) { boolFilter.push({ diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index 157e014bee424..dce8e49deec41 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -111,6 +111,7 @@ export function KueryBar() { query: inputValue, selectionStart, selectionEnd: selectionStart, + useTimeRange: true, })) || [] ) .filter((suggestion) => !startsWith(suggestion.text, 'span.')) diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts index 441e5a6f775dd..cab3a657b7b6b 100644 --- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -27,7 +27,7 @@ const wrapAsSuggestions = (start: number, end: number, query: string, values: st export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => { return async ( - { indexPatterns, boolFilter, signal }, + { indexPatterns, boolFilter, useTimeRange, signal }, { start, end, prefix, suffix, fieldName, nestedPath } ): Promise => { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; @@ -49,6 +49,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => { field, query, boolFilter, + useTimeRange, signal, }).then((valueSuggestions) => { const quotedValues = valueSuggestions.map((value) => diff --git a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx index 61402008363da..f42f0e7385eb6 100644 --- a/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/kuery_bar/kuery_bar.tsx @@ -67,7 +67,7 @@ export function KueryBar({ let currentRequestCheck: string; const [getUrlParams, updateUrlParams] = useUrlParams(); - const { search: kuery, dateRangeStart, dateRangeEnd } = getUrlParams(); + const { search: kuery } = getUrlParams(); useEffect(() => { updateSearchText(kuery); @@ -105,16 +105,7 @@ export function KueryBar({ query: inputValue, selectionStart: selectionStart || 0, selectionEnd: selectionStart || 0, - boolFilter: [ - { - range: { - '@timestamp': { - gte: dateRangeStart, - lte: dateRangeEnd, - }, - }, - }, - ], + useTimeRange: true, })) || [] ).filter((suggestion: QuerySuggestion) => !suggestion.text.startsWith('span.')); if (currentRequest !== currentRequestCheck) { diff --git a/x-pack/test/functional/apps/discover/value_suggestions.ts b/x-pack/test/functional/apps/discover/value_suggestions.ts index 54720b94172f6..5d75927655228 100644 --- a/x-pack/test/functional/apps/discover/value_suggestions.ts +++ b/x-pack/test/functional/apps/discover/value_suggestions.ts @@ -10,13 +10,14 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'timePicker']); describe('value suggestions', function describeIndexTests() { before(async function () { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('dashboard/drilldowns'); await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); }); after(async () => { diff --git a/x-pack/test/functional/apps/maps/vector_styling.js b/x-pack/test/functional/apps/maps/vector_styling.js index e4c5eaf892c76..56a8188c8341b 100644 --- a/x-pack/test/functional/apps/maps/vector_styling.js +++ b/x-pack/test/functional/apps/maps/vector_styling.js @@ -7,13 +7,17 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['maps']); + const PageObjects = getPageObjects(['maps', 'timePicker']); const security = getService('security'); describe('vector styling', () => { before(async () => { await security.testUser.setRoles(['test_logstash_reader', 'global_maps_all']); await PageObjects.maps.loadSavedMap('document example'); + await PageObjects.timePicker.setAbsoluteRange( + 'Mar 1, 2015 @ 00:00:00.000', + 'Mar 1, 2016 @ 00:00:00.000' + ); }); after(async () => { await PageObjects.maps.refreshAndClearUnsavedChangesWarning(); From c7f085ff0bf22f0b53f5b1268c6b846a05f32954 Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 10 Nov 2020 10:52:20 -0800 Subject: [PATCH 64/87] [Enterprise Search] Tech debt/cleanup: remove I/E/T Typescript prefixes (#83027) * [All] Remove prefixes on simple self-contained type defs - Types should not be exported - Types should not be used outside each affected file * [All][kea] Remove ts prefixes and unnecessary exports Kea now takes care of type checking for us, so there should virtually never be a need to export our values and actions interfaces going forward * [shared] Remove createHref type prefixes * [shared] Remove breadcrumb prefixes * [shared] Remove telemetry prefixes * [shared] remove types.ts Opionionated change: it was only being used for IFlashMessage, and at this point I think it's more useful to go in one level deeper to grab the type you need * [server] remove route dependencies prefixes * [server] Various type cleanups - plugin.ts - remove unnecessary export - MockRouter - remove prefix for request type, change IMockRouter to match Kibana's IRouter - check_access - remove prefixes - callEnterpriseSearchConfigAPI - remove prefixes - EnterpriseSearchRequestHandler - remove prefixes * [common] Remove InitialAppData prefix + remove unnecessary export from public/plugin.ts * [common] Remove Meta prefixes * [common] Remove configured limits prefixes * [AS] Remove Account and Role prefixes * [AS] Remove Engine prefixes * [AS] Remove credentials prefixes * [AS] Remove log settings prefixes * [WS] Remove account/organization/initial data prefixes * [WS] Remove group(s), user, & content source prefixes + GroupLogic and GroupsLogic refactor - remove unnecessary defs in actions, it's already defined in the Actions interface above and in some cases (e.g. old History param) is causing out of date issues * [WS] Misc type fixes - TSpacerSize -> SpaceSizeTypes - SourcePriority - remove prefixes - IComponentLoader - this isn't used anywhere else and appears to be component props so it probably should live only within component_loader.tsx - Remove recent feed activity prefix * [WS][Opinionated] Move interfaces not used in server/ out of common/ and to public/ --- .../common/types/app_search.ts | 4 +- .../enterprise_search/common/types/index.ts | 28 +++---- .../common/types/workplace_search.ts | 58 ++------------ .../__mocks__/mount_async.mock.tsx | 4 +- .../applications/app_search/app_logic.ts | 18 ++--- .../form_components/key_read_write_access.tsx | 6 +- .../credentials_flyout/header.test.tsx | 4 +- .../credentials_list.test.tsx | 6 +- .../credentials_list/credentials_list.tsx | 20 ++--- .../credentials/credentials_list/key.tsx | 4 +- .../credentials/credentials_logic.ts | 40 +++++----- .../components/credentials/types.ts | 10 +-- .../credentials/utils/api_token_sort.test.ts | 4 +- .../credentials/utils/api_token_sort.ts | 4 +- .../utils/get_engines_display_text.test.tsx | 4 +- .../utils/get_engines_display_text.tsx | 4 +- .../utils/get_mode_display_text.test.ts | 4 +- .../utils/get_mode_display_text.ts | 4 +- .../components/engines/engines_overview.tsx | 8 +- .../components/engines/engines_table.tsx | 18 ++--- .../log_retention/log_retention_logic.test.ts | 30 ++++---- .../log_retention/log_retention_logic.ts | 26 +++---- .../settings/log_retention/types.ts | 26 +++---- .../utils/convert_log_retention.ts | 30 ++++---- .../public/applications/app_search/index.tsx | 10 +-- .../public/applications/app_search/types.ts | 4 +- .../app_search/utils/role/index.ts | 22 +++--- .../components/product_card/product_card.tsx | 4 +- .../product_selector/product_selector.tsx | 4 +- .../applications/enterprise_search/index.tsx | 4 +- .../public/applications/index.tsx | 4 +- .../flash_messages/flash_messages_logic.ts | 6 +- .../flash_messages/handle_api_errors.ts | 9 +-- .../shared/flash_messages/index.ts | 8 +- .../shared/hidden_text/hidden_text.tsx | 8 +- .../applications/shared/http/http_logic.ts | 10 +-- .../public/applications/shared/http/index.ts | 2 +- .../shared/kibana/kibana_logic.ts | 14 ++-- .../kibana_chrome/generate_breadcrumbs.ts | 16 ++-- .../shared/kibana_chrome/generate_title.ts | 10 +-- .../shared/kibana_chrome/set_chrome.tsx | 12 +-- .../applications/shared/layout/layout.tsx | 4 +- .../applications/shared/layout/side_nav.tsx | 12 +-- .../shared/licensing/licensing_logic.ts | 10 +-- .../react_router_helpers/create_href.ts | 8 +- .../shared/react_router_helpers/eui_link.tsx | 16 ++-- .../shared/react_router_helpers/index.ts | 2 +- .../react_router_helpers/link_events.ts | 10 +-- .../shared/setup_guide/setup_guide.tsx | 4 +- .../shared/table_header/table_header.tsx | 4 +- .../shared/telemetry/send_telemetry.tsx | 11 ++- .../shared/telemetry/telemetry_logic.ts | 24 +++--- .../shared/truncate/truncated_content.tsx | 4 +- .../public/applications/shared/types.ts | 7 -- .../workplace_search/app_logic.ts | 20 ++--- .../components/shared/api_key/api_key.tsx | 4 +- .../component_loader/component_loader.tsx | 6 +- .../content_section/content_section.tsx | 8 +- .../credential_item/credential_item.tsx | 4 +- .../source_config_fields.tsx | 4 +- .../shared/source_icon/source_icon.tsx | 4 +- .../shared/source_row/source_row.tsx | 8 +- .../shared/sources_table/sources_table.tsx | 8 +- .../table_pagination_bar.tsx | 4 +- .../components/shared/user_icon/user_icon.tsx | 4 +- .../components/shared/user_row/user_row.tsx | 8 +- .../view_content_header.tsx | 4 +- .../applications/workplace_search/index.tsx | 6 +- .../applications/workplace_search/types.ts | 52 +++++++++++-- .../groups/__mocks__/group_logic.mock.ts | 12 ++- .../groups/__mocks__/groups_logic.mock.ts | 12 ++- .../components/filterable_users_list.test.tsx | 4 +- .../components/filterable_users_list.tsx | 10 +-- .../components/filterable_users_popover.tsx | 8 +- .../groups/components/group_manager_modal.tsx | 8 +- .../views/groups/components/group_row.tsx | 4 +- .../components/group_row_sources_dropdown.tsx | 8 +- .../components/group_row_users_dropdown.tsx | 4 +- .../group_source_prioritization.tsx | 4 +- .../groups/components/group_sources.test.tsx | 4 +- .../views/groups/components/group_sources.tsx | 8 +- .../groups/components/group_users.test.tsx | 4 +- .../views/groups/components/group_users.tsx | 8 +- .../components/group_users_table.test.tsx | 4 +- .../groups/components/group_users_table.tsx | 4 +- .../groups/components/source_option_item.tsx | 8 +- .../views/groups/components/sources_list.tsx | 8 +- .../groups/components/user_option_item.tsx | 8 +- .../views/groups/group_logic.ts | 75 +++++++++---------- .../views/groups/groups_logic.ts | 72 +++++++++--------- .../overview/__mocks__/overview_logic.mock.ts | 4 +- .../views/overview/onboarding_card.tsx | 4 +- .../views/overview/overview_logic.ts | 14 ++-- .../views/overview/recent_activity.tsx | 6 +- .../views/overview/statistic_card.tsx | 4 +- .../enterprise_search/public/plugin.ts | 7 +- .../server/__mocks__/router.mock.ts | 14 ++-- .../server/collectors/app_search/telemetry.ts | 8 +- .../collectors/enterprise_search/telemetry.ts | 8 +- .../server/collectors/lib/telemetry.ts | 4 +- .../collectors/workplace_search/telemetry.ts | 8 +- .../server/lib/check_access.ts | 6 +- .../lib/enterprise_search_config_api.ts | 12 +-- .../lib/enterprise_search_request_handler.ts | 12 +-- .../enterprise_search/server/plugin.ts | 4 +- .../server/routes/app_search/credentials.ts | 4 +- .../server/routes/app_search/engines.ts | 8 +- .../server/routes/app_search/index.ts | 4 +- .../server/routes/app_search/settings.ts | 4 +- .../routes/enterprise_search/config_data.ts | 4 +- .../routes/enterprise_search/telemetry.ts | 8 +- .../server/routes/workplace_search/groups.ts | 19 ++--- .../server/routes/workplace_search/index.ts | 4 +- .../routes/workplace_search/overview.ts | 4 +- 114 files changed, 597 insertions(+), 625 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/types.ts diff --git a/x-pack/plugins/enterprise_search/common/types/app_search.ts b/x-pack/plugins/enterprise_search/common/types/app_search.ts index 203b77834bc15..9f754412ec730 100644 --- a/x-pack/plugins/enterprise_search/common/types/app_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/app_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface IAccount { +export interface Account { accountId: string; onboardingComplete: boolean; role: { @@ -21,7 +21,7 @@ export interface IAccount { }; } -export interface IConfiguredLimits { +export interface ConfiguredLimits { engine: { maxDocumentByteSize: number; maxEnginesPerMetaEngine: number; diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index 1006d39138759..39d9aa8607bc2 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -5,39 +5,39 @@ */ import { - IAccount as IAppSearchAccount, - IConfiguredLimits as IAppSearchConfiguredLimits, + Account as AppSearchAccount, + ConfiguredLimits as AppSearchConfiguredLimits, } from './app_search'; import { - IWorkplaceSearchInitialData, - IConfiguredLimits as IWorkplaceSearchConfiguredLimits, + WorkplaceSearchInitialData, + ConfiguredLimits as WorkplaceSearchConfiguredLimits, } from './workplace_search'; -export interface IInitialAppData { +export interface InitialAppData { readOnlyMode?: boolean; ilmEnabled?: boolean; isFederatedAuth?: boolean; - configuredLimits?: IConfiguredLimits; + configuredLimits?: ConfiguredLimits; access?: { hasAppSearchAccess: boolean; hasWorkplaceSearchAccess: boolean; }; - appSearch?: IAppSearchAccount; - workplaceSearch?: IWorkplaceSearchInitialData; + appSearch?: AppSearchAccount; + workplaceSearch?: WorkplaceSearchInitialData; } -export interface IConfiguredLimits { - appSearch: IAppSearchConfiguredLimits; - workplaceSearch: IWorkplaceSearchConfiguredLimits; +export interface ConfiguredLimits { + appSearch: AppSearchConfiguredLimits; + workplaceSearch: WorkplaceSearchConfiguredLimits; } -export interface IMetaPage { +export interface MetaPage { current: number; size: number; total_pages: number; total_results: number; } -export interface IMeta { - page: IMetaPage; +export interface Meta { + page: MetaPage; } diff --git a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts b/x-pack/plugins/enterprise_search/common/types/workplace_search.ts index 886597fcd9891..883cc6939b4bc 100644 --- a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/workplace_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface IAccount { +export interface Account { id: string; groups: string[]; isAdmin: boolean; @@ -14,65 +14,19 @@ export interface IAccount { viewedOnboardingPage: boolean; } -export interface IOrganization { +export interface Organization { name: string; defaultOrgName: string; } -export interface IWorkplaceSearchInitialData { - organization: IOrganization; - account: IAccount; +export interface WorkplaceSearchInitialData { + organization: Organization; + account: Account; } -export interface IConfiguredLimits { +export interface ConfiguredLimits { customApiSource: { maxDocumentByteSize: number; totalFields: number; }; } - -export interface IGroup { - id: string; - name: string; - createdAt: string; - updatedAt: string; - contentSources: IContentSource[]; - users: IUser[]; - usersCount: number; - color?: string; -} - -export interface IGroupDetails extends IGroup { - contentSources: IContentSourceDetails[]; - canEditGroup: boolean; - canDeleteGroup: boolean; -} - -export interface IUser { - id: string; - name: string | null; - initials: string; - pictureUrl: string | null; - color: string; - email: string; - role?: string; - groupIds: string[]; -} - -export interface IContentSource { - id: string; - serviceType: string; - name: string; -} - -export interface IContentSourceDetails extends IContentSource { - status: string; - statusMessage: string; - documentCount: string; - isFederatedSource: boolean; - searchable: boolean; - supportedByLicense: boolean; - errorReason: number; - allowsReauth: boolean; - boost: number; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx index a33e116c7ca72..a3d817e1da904 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx @@ -20,13 +20,13 @@ import { mountWithIntl } from './'; * const wrapper = mountAsync(); */ -interface IOptions { +interface Options { i18n?: boolean; } export const mountAsync = async ( children: React.ReactElement, - options: IOptions + options: Options ): Promise => { let wrapper: ReactWrapper | undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 932e84af45c2b..2b475073c6ea5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -6,24 +6,24 @@ import { kea, MakeLogicType } from 'kea'; -import { IInitialAppData } from '../../../common/types'; -import { IConfiguredLimits, IAccount, IRole } from './types'; +import { InitialAppData } from '../../../common/types'; +import { ConfiguredLimits, Account, Role } from './types'; import { getRoleAbilities } from './utils/role'; -export interface IAppValues { +interface AppValues { hasInitialized: boolean; ilmEnabled: boolean; - configuredLimits: Partial; - account: Partial; - myRole: Partial; + configuredLimits: Partial; + account: Partial; + myRole: Partial; } -export interface IAppActions { - initializeAppData(props: IInitialAppData): Required; +interface AppActions { + initializeAppData(props: InitialAppData): Required; setOnboardingComplete(): boolean; } -export const AppLogic = kea>({ +export const AppLogic = kea>({ path: ['enterprise_search', 'app_search', 'app_logic'], actions: { initializeAppData: (props) => props, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx index a02b00b6ad377..d96c57b3c8bc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx @@ -10,7 +10,7 @@ import { EuiCheckbox, EuiText, EuiTitle, EuiSpacer, EuiPanel } from '@elastic/eu import { i18n } from '@kbn/i18n'; import { CredentialsLogic } from '../../credentials_logic'; -import { ITokenReadWrite } from '../../types'; +import { TokenReadWrite } from '../../types'; export const FormKeyReadWriteAccess: React.FC = () => { const { setTokenReadWrite } = useActions(CredentialsLogic); @@ -37,7 +37,7 @@ export const FormKeyReadWriteAccess: React.FC = () => { name="read" id="read" checked={activeApiToken.read} - onChange={(e) => setTokenReadWrite(e.target as ITokenReadWrite)} + onChange={(e) => setTokenReadWrite(e.target as TokenReadWrite)} label={i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel', { defaultMessage: 'Read Access' } @@ -47,7 +47,7 @@ export const FormKeyReadWriteAccess: React.FC = () => { name="write" id="write" checked={activeApiToken.write} - onChange={(e) => setTokenReadWrite(e.target as ITokenReadWrite)} + onChange={(e) => setTokenReadWrite(e.target as TokenReadWrite)} label={i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel', { defaultMessage: 'Write Access' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx index a8d9505136faa..803789ebee568 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx @@ -11,12 +11,12 @@ import { shallow } from 'enzyme'; import { EuiFlyoutHeader } from '@elastic/eui'; import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { CredentialsFlyoutHeader } from './header'; describe('CredentialsFlyoutHeader', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx index 97d29b9333f4b..4f5ded0a3ccc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiBasicTable, EuiCopy, EuiEmptyPrompt } from '@elastic/eui'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { ApiTokenTypes } from '../constants'; import { HiddenText } from '../../../../shared/hidden_text'; @@ -18,7 +18,7 @@ import { Key } from './key'; import { CredentialsList } from './credentials_list'; describe('Credentials', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, @@ -77,7 +77,7 @@ describe('Credentials', () => { }); const wrapper = shallow(); const { items } = wrapper.find(EuiBasicTable).props(); - expect(items.map((i: IApiToken) => i.id)).toEqual([undefined, 1, 2]); + expect(items.map((i: ApiToken) => i.id)).toEqual([undefined, 1, 2]); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index f9752dca582e1..9240bade4975e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { CredentialsLogic } from '../credentials_logic'; import { Key } from './key'; import { HiddenText } from '../../../../shared/hidden_text'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; import { apiTokenSort } from '../utils/api_token_sort'; import { getModeDisplayText, getEnginesDisplayText } from '../utils'; @@ -26,21 +26,21 @@ export const CredentialsList: React.FC = () => { const items = useMemo(() => apiTokens.slice().sort(apiTokenSort), [apiTokens]); - const columns: Array> = [ + const columns: Array> = [ { name: 'Name', width: '12%', - render: (token: IApiToken) => token.name, + render: (token: ApiToken) => token.name, }, { name: 'Type', width: '15%', - render: (token: IApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], + render: (token: ApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], }, { name: 'Key', width: '36%', - render: (token: IApiToken) => { + render: (token: ApiToken) => { const { key } = token; if (!key) return null; return ( @@ -64,12 +64,12 @@ export const CredentialsList: React.FC = () => { { name: 'Modes', width: '10%', - render: (token: IApiToken) => getModeDisplayText(token), + render: (token: ApiToken) => getModeDisplayText(token), }, { name: 'Engines', width: '18%', - render: (token: IApiToken) => getEnginesDisplayText(token), + render: (token: ApiToken) => getEnginesDisplayText(token), }, { actions: [ @@ -83,7 +83,7 @@ export const CredentialsList: React.FC = () => { type: 'icon', icon: 'pencil', color: 'primary', - onClick: (token: IApiToken) => showCredentialsForm(token), + onClick: (token: ApiToken) => showCredentialsForm(token), }, { name: i18n.translate('xpack.enterpriseSearch.actions.delete', { @@ -95,7 +95,7 @@ export const CredentialsList: React.FC = () => { type: 'icon', icon: 'trash', color: 'danger', - onClick: (token: IApiToken) => deleteApiKey(token.name), + onClick: (token: ApiToken) => deleteApiKey(token.name), }, ], }, @@ -108,7 +108,7 @@ export const CredentialsList: React.FC = () => { hidePerPageOptions: true, }; - const onTableChange = ({ page }: CriteriaWithPagination) => { + const onTableChange = ({ page }: CriteriaWithPagination) => { const { index: current } = page; fetchCredentials(current + 1); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx index 5c0c24ec733a4..fa2d124cbccdf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -interface IProps { +interface Props { copy: () => void; toggleIsHidden: () => void; isHidden: boolean; text: React.ReactNode; } -export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { +export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { const hideIcon = isHidden ? 'eye' : 'eyeClosed'; const hideIconLabel = isHidden ? i18n.translate('xpack.enterpriseSearch.appSearch.credentials.showApiKey', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts index 7b8b864b3a317..166cbae9a4512 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts @@ -17,11 +17,11 @@ import { } from '../../../shared/flash_messages'; import { AppLogic } from '../../app_logic'; -import { IMeta } from '../../../../../common/types'; -import { IEngine } from '../../types'; -import { IApiToken, ICredentialsDetails, ITokenReadWrite } from './types'; +import { Meta } from '../../../../../common/types'; +import { Engine } from '../../types'; +import { ApiToken, CredentialsDetails, TokenReadWrite } from './types'; -export const defaultApiToken: IApiToken = { +export const defaultApiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, @@ -29,21 +29,21 @@ export const defaultApiToken: IApiToken = { access_all_engines: true, }; -interface ICredentialsLogicActions { +interface CredentialsLogicActions { addEngineName(engineName: string): string; onApiKeyDelete(tokenName: string): string; - onApiTokenCreateSuccess(apiToken: IApiToken): IApiToken; + onApiTokenCreateSuccess(apiToken: ApiToken): ApiToken; onApiTokenError(formErrors: string[]): string[]; - onApiTokenUpdateSuccess(apiToken: IApiToken): IApiToken; + onApiTokenUpdateSuccess(apiToken: ApiToken): ApiToken; removeEngineName(engineName: string): string; setAccessAllEngines(accessAll: boolean): boolean; - setCredentialsData(meta: IMeta, apiTokens: IApiToken[]): { meta: IMeta; apiTokens: IApiToken[] }; - setCredentialsDetails(details: ICredentialsDetails): ICredentialsDetails; + setCredentialsData(meta: Meta, apiTokens: ApiToken[]): { meta: Meta; apiTokens: ApiToken[] }; + setCredentialsDetails(details: CredentialsDetails): CredentialsDetails; setNameInputBlurred(isBlurred: boolean): boolean; - setTokenReadWrite(tokenReadWrite: ITokenReadWrite): ITokenReadWrite; + setTokenReadWrite(tokenReadWrite: TokenReadWrite): TokenReadWrite; setTokenName(name: string): string; setTokenType(tokenType: string): string; - showCredentialsForm(apiToken?: IApiToken): IApiToken; + showCredentialsForm(apiToken?: ApiToken): ApiToken; hideCredentialsForm(): { value: boolean }; resetCredentials(): { value: boolean }; initializeCredentialsData(): { value: boolean }; @@ -54,25 +54,25 @@ interface ICredentialsLogicActions { onEngineSelect(engineName: string): string; } -interface ICredentialsLogicValues { - activeApiToken: IApiToken; +interface CredentialsLogicValues { + activeApiToken: ApiToken; activeApiTokenExists: boolean; activeApiTokenRawName: string; - apiTokens: IApiToken[]; + apiTokens: ApiToken[]; dataLoading: boolean; - engines: IEngine[]; + engines: Engine[]; formErrors: string[]; isCredentialsDataComplete: boolean; isCredentialsDetailsComplete: boolean; fullEngineAccessChecked: boolean; - meta: Partial; + meta: Partial; nameInputBlurred: boolean; shouldShowCredentialsForm: boolean; } -export const CredentialsLogic = kea< - MakeLogicType ->({ +type CredentialsLogicType = MakeLogicType; // If we leave this inline, Prettier does some horrifying indenting nonsense :/ + +export const CredentialsLogic = kea({ path: ['enterprise_search', 'app_search', 'credentials_logic'], actions: () => ({ addEngineName: (engineName) => engineName, @@ -267,7 +267,7 @@ export const CredentialsLogic = kea< onApiTokenChange: async () => { const { id, name, engines, type, read, write } = values.activeApiToken; - const data: IApiToken = { + const data: ApiToken = { name, type, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts index 9ca4d086d55c8..23f78b44c0db5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEngine } from '../../types'; +import { Engine } from '../../types'; import { ApiTokenTypes } from './constants'; -export interface ICredentialsDetails { - engines: IEngine[]; +export interface CredentialsDetails { + engines: Engine[]; } -export interface IApiToken { +export interface ApiToken { access_all_engines?: boolean; key?: string; engines?: string[]; @@ -22,7 +22,7 @@ export interface IApiToken { write?: boolean; } -export interface ITokenReadWrite { +export interface TokenReadWrite { name: 'read' | 'write'; checked: boolean; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts index 84818322b3570..2287125bb5eb8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts @@ -7,10 +7,10 @@ import { apiTokenSort } from '.'; import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; describe('apiTokenSort', () => { - const apiToken: IApiToken = { + const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts index 80a46f30e0930..b9fb501ccabe2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const apiTokenSort = (apiTokenA: IApiToken, apiTokenB: IApiToken): number => { +export const apiTokenSort = (apiTokenA: ApiToken, apiTokenB: ApiToken): number => { if (!apiTokenA.id) { return -1; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx index b06ed63f8616c..bee19a44facd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { shallow } from 'enzyme'; import { getEnginesDisplayText } from './get_engines_display_text'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { ApiTokenTypes } from '../constants'; -const apiToken: IApiToken = { +const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx index 1b216c46307db..fb23551302f3b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { ApiTokenTypes, ALL } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const getEnginesDisplayText = (apiToken: IApiToken): JSX.Element | string => { +export const getEnginesDisplayText = (apiToken: ApiToken): JSX.Element | string => { const { type, access_all_engines: accessAll, engines = [] } = apiToken; if (type === ApiTokenTypes.Admin) { return '--'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts index b2083f22c8e1c..46b4c9b2c786c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts @@ -5,11 +5,11 @@ */ import { ApiTokenTypes } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; import { getModeDisplayText } from './get_mode_display_text'; -const apiToken: IApiToken = { +const apiToken: ApiToken = { name: '', type: ApiTokenTypes.Private, read: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts index 9c8758d83882d..b150aa2cfc3be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts @@ -5,9 +5,9 @@ */ import { ApiTokenTypes, READ_ONLY, READ_WRITE, SEARCH_DISPLAY, WRITE_ONLY } from '../constants'; -import { IApiToken } from '../types'; +import { ApiToken } from '../types'; -export const getModeDisplayText = (apiToken: IApiToken): string => { +export const getModeDisplayText = (apiToken: ApiToken): string => { const { read = false, write = false, type } = apiToken; switch (type) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 559fef695d63b..0381c3806fec7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -28,11 +28,11 @@ import { EnginesTable } from './engines_table'; import './engines_overview.scss'; -interface IGetEnginesParams { +interface GetEnginesParams { type: string; pageIndex: number; } -interface ISetEnginesCallbacks { +interface SetEnginesCallbacks { setResults: React.Dispatch>; setResultsTotal: React.Dispatch>; } @@ -49,12 +49,12 @@ export const EnginesOverview: React.FC = () => { const [metaEnginesPage, setMetaEnginesPage] = useState(1); const [metaEnginesTotal, setMetaEnginesTotal] = useState(0); - const getEnginesData = async ({ type, pageIndex }: IGetEnginesParams) => { + const getEnginesData = async ({ type, pageIndex }: GetEnginesParams) => { return await http.get('/api/app_search/engines', { query: { type, pageIndex }, }); }; - const setEnginesData = async (params: IGetEnginesParams, callbacks: ISetEnginesCallbacks) => { + const setEnginesData = async (params: GetEnginesParams, callbacks: SetEnginesCallbacks) => { const response = await getEnginesData(params); callbacks.setResults(response.results); 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 a9cf64b3dffda..9591bbda1f7c2 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 @@ -16,28 +16,28 @@ import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; -interface IEnginesTableData { +interface EnginesTableData { name: string; created_at: string; document_count: number; field_count: number; } -interface IEnginesTablePagination { +interface EnginesTablePagination { totalEngines: number; pageIndex: number; onPaginate(pageIndex: number): void; } -interface IEnginesTableProps { - data: IEnginesTableData[]; - pagination: IEnginesTablePagination; +interface EnginesTableProps { + data: EnginesTableData[]; + pagination: EnginesTablePagination; } -interface IOnChange { +interface OnChange { page: { index: number; }; } -export const EnginesTable: React.FC = ({ +export const EnginesTable: React.FC = ({ data, pagination: { totalEngines, pageIndex, onPaginate }, }) => { @@ -52,7 +52,7 @@ export const EnginesTable: React.FC = ({ }), }); - const columns: Array> = [ + const columns: Array> = [ { field: 'name', name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { @@ -144,7 +144,7 @@ export const EnginesTable: React.FC = ({ totalItemCount: totalEngines, hidePerPageOptions: true, }} - onChange={({ page }: IOnChange) => { + onChange={({ page }: OnChange) => { const { index } = page; onPaginate(index + 1); // Note on paging - App Search's API pages start at 1, EuiBasicTables' pages start at 0 }} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts index 367c7b085123f..c86d7e3e915e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.test.ts @@ -17,7 +17,7 @@ jest.mock('../../../../shared/flash_messages', () => ({ })); import { flashAPIErrors } from '../../../../shared/flash_messages'; -import { ELogRetentionOptions } from './types'; +import { LogRetentionOptions } from './types'; import { LogRetentionLogic } from './log_retention_logic'; describe('LogRetentionLogic', () => { @@ -87,11 +87,11 @@ describe('LogRetentionLogic', () => { it('should be set to the provided value', () => { mount(); - LogRetentionLogic.actions.setOpenedModal(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.setOpenedModal(LogRetentionOptions.Analytics); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, - openedModal: ELogRetentionOptions.Analytics, + openedModal: LogRetentionOptions.Analytics, }); }); }); @@ -194,10 +194,10 @@ describe('LogRetentionLogic', () => { describe('openedModal', () => { it('should be reset to null', () => { mount({ - openedModal: ELogRetentionOptions.Analytics, + openedModal: LogRetentionOptions.Analytics, }); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, @@ -211,7 +211,7 @@ describe('LogRetentionLogic', () => { const promise = Promise.resolve(TYPICAL_SERVER_LOG_RETENTION); http.put.mockReturnValue(promise); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); expect(http.put).toHaveBeenCalledWith('/api/app_search/log_settings', { body: JSON.stringify({ @@ -233,7 +233,7 @@ describe('LogRetentionLogic', () => { const promise = Promise.reject('An error occured'); http.put.mockReturnValue(promise); - LogRetentionLogic.actions.saveLogRetention(ELogRetentionOptions.Analytics, true); + LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); try { await promise; @@ -252,7 +252,7 @@ describe('LogRetentionLogic', () => { isLogRetentionUpdating: false, }); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.values).toEqual({ ...DEFAULT_VALUES, @@ -264,17 +264,17 @@ describe('LogRetentionLogic', () => { it('will call setOpenedModal if already enabled', () => { mount({ logRetention: { - [ELogRetentionOptions.Analytics]: { + [LogRetentionOptions.Analytics]: { enabled: true, }, }, }); jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.actions.setOpenedModal).toHaveBeenCalledWith( - ELogRetentionOptions.Analytics + LogRetentionOptions.Analytics ); }); }); @@ -337,17 +337,17 @@ describe('LogRetentionLogic', () => { it('will call saveLogRetention if NOT already enabled', () => { mount({ logRetention: { - [ELogRetentionOptions.Analytics]: { + [LogRetentionOptions.Analytics]: { enabled: false, }, }, }); jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.Analytics); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( - ELogRetentionOptions.Analytics, + LogRetentionOptions.Analytics, true ); }); @@ -359,7 +359,7 @@ describe('LogRetentionLogic', () => { jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - LogRetentionLogic.actions.toggleLogRetention(ELogRetentionOptions.API); + LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.API); expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts index 28830f2edb1d9..31fc41213492d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_logic.ts @@ -6,31 +6,31 @@ import { kea, MakeLogicType } from 'kea'; -import { ELogRetentionOptions, ILogRetention, ILogRetentionServer } from './types'; +import { LogRetentionOptions, LogRetention, LogRetentionServer } from './types'; import { HttpLogic } from '../../../../shared/http'; import { flashAPIErrors } from '../../../../shared/flash_messages'; import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; -interface ILogRetentionActions { +interface LogRetentionActions { clearLogRetentionUpdating(): { value: boolean }; closeModals(): { value: boolean }; fetchLogRetention(): { value: boolean }; saveLogRetention( - option: ELogRetentionOptions, + option: LogRetentionOptions, enabled: boolean - ): { option: ELogRetentionOptions; enabled: boolean }; - setOpenedModal(option: ELogRetentionOptions): { option: ELogRetentionOptions }; - toggleLogRetention(option: ELogRetentionOptions): { option: ELogRetentionOptions }; - updateLogRetention(logRetention: ILogRetention): { logRetention: ILogRetention }; + ): { option: LogRetentionOptions; enabled: boolean }; + setOpenedModal(option: LogRetentionOptions): { option: LogRetentionOptions }; + toggleLogRetention(option: LogRetentionOptions): { option: LogRetentionOptions }; + updateLogRetention(logRetention: LogRetention): { logRetention: LogRetention }; } -interface ILogRetentionValues { - logRetention: ILogRetention | null; +interface LogRetentionValues { + logRetention: LogRetention | null; isLogRetentionUpdating: boolean; - openedModal: ELogRetentionOptions | null; + openedModal: LogRetentionOptions | null; } -export const LogRetentionLogic = kea>({ +export const LogRetentionLogic = kea>({ path: ['enterprise_search', 'app_search', 'log_retention_logic'], actions: () => ({ clearLogRetentionUpdating: true, @@ -72,7 +72,7 @@ export const LogRetentionLogic = kea ({ - [ELogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( - logRetention[ELogRetentionOptions.Analytics] + logRetention: LogRetentionServer +): LogRetention => ({ + [LogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( + logRetention[LogRetentionOptions.Analytics] ), - [ELogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( - logRetention[ELogRetentionOptions.API] + [LogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( + logRetention[LogRetentionOptions.API] ), }); @@ -29,7 +29,7 @@ const convertLogRetentionSettingsFromServerToClient = ({ disabled_at: disabledAt, enabled, retention_policy: retentionPolicy, -}: ILogRetentionServerSettings): ILogRetentionSettings => ({ +}: LogRetentionServerSettings): LogRetentionSettings => ({ disabledAt, enabled, retentionPolicy: @@ -39,7 +39,7 @@ const convertLogRetentionSettingsFromServerToClient = ({ const convertLogRetentionPolicyFromServerToClient = ({ min_age_days: minAgeDays, is_default: isDefault, -}: ILogRetentionServerPolicy): ILogRetentionPolicy => ({ +}: LogRetentionServerPolicy): LogRetentionPolicy => ({ isDefault, minAgeDays, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 4571ef10286e4..743cf63fb4bc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -12,7 +12,7 @@ import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { KibanaLogic } from '../shared/kibana'; import { HttpLogic } from '../shared/http'; import { AppLogic } from './app_logic'; -import { IInitialAppData } from '../../../common/types'; +import { InitialAppData } from '../../../common/types'; import { APP_SEARCH_PLUGIN } from '../../../common/constants'; import { Layout, SideNav, SideNavLink } from '../shared/layout'; @@ -36,7 +36,7 @@ import { Settings, SETTINGS_TITLE } from './components/settings'; import { Credentials, CREDENTIALS_TITLE } from './components/credentials'; import { ROLE_MAPPINGS_TITLE } from './components/role_mappings'; -export const AppSearch: React.FC = (props) => { +export const AppSearch: React.FC = (props) => { const { config } = useValues(KibanaLogic); return !config.host ? : ; }; @@ -52,7 +52,7 @@ export const AppSearchUnconfigured: React.FC = () => ( ); -export const AppSearchConfigured: React.FC = (props) => { +export const AppSearchConfigured: React.FC = (props) => { const { initializeAppData } = useActions(AppLogic); const { hasInitialized } = useValues(AppLogic); const { errorConnecting, readOnlyMode } = useValues(HttpLogic); @@ -100,11 +100,11 @@ export const AppSearchConfigured: React.FC = (props) => { ); }; -interface IAppSearchNavProps { +interface AppSearchNavProps { subNav?: React.ReactNode; } -export const AppSearchNav: React.FC = ({ subNav }) => { +export const AppSearchNav: React.FC = ({ subNav }) => { const { myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, } = useValues(AppLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts index 568a0a3365982..ce7d906555d34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts @@ -5,9 +5,9 @@ */ export * from '../../../common/types/app_search'; -export { IRole, TRole, TAbility } from './utils/role'; +export { Role, RoleTypes, AbilityTypes } from './utils/role'; -export interface IEngine { +export interface Engine { name: string; type: string; language: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts index a935fa657738c..21a80bc0c208f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts @@ -4,18 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IAccount } from '../../types'; +import { Account } from '../../types'; -export type TRole = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; -export type TAbility = 'manage' | 'edit' | 'view'; +export type RoleTypes = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; +export type AbilityTypes = 'manage' | 'edit' | 'view'; -export interface IRole { +export interface Role { id: string; - roleType: TRole; - availableRoleTypes: TRole[]; + roleType: RoleTypes; + availableRoleTypes: RoleTypes[]; credentialTypes: string[]; canAccessAllEngines: boolean; - can(action: TAbility, subject: string): boolean; + can(action: AbilityTypes, subject: string): boolean; canViewMetaEngines: boolean; canViewAccountCredentials: boolean; canViewEngineAnalytics: boolean; @@ -48,10 +48,10 @@ export interface IRole { * Transforms the `role` data we receive from the Enterprise Search * server into a more convenient format for front-end use */ -export const getRoleAbilities = (role: IAccount['role']): IRole => { +export const getRoleAbilities = (role: Account['role']): Role => { // Role ability function helpers const myRole = { - can: (action: TAbility, subject: string): boolean => { + can: (action: AbilityTypes, subject: string): boolean => { return ( role?.ability?.manage?.includes(subject) || (Array.isArray(role.ability[action]) && role.ability[action].includes(subject)) @@ -63,8 +63,8 @@ export const getRoleAbilities = (role: IAccount['role']): IRole => { // Clone top-level role props, and move some props out of `ability` and into the top-level for convenience const topLevelProps = { id: role.id, - roleType: role.roleType as TRole, - availableRoleTypes: role.ability.availableRoleTypes as TRole[], + roleType: role.roleType as RoleTypes, + availableRoleTypes: role.ability.availableRoleTypes as RoleTypes[], credentialTypes: role.ability.credentialTypes, }; 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 ee778f49ef5b6..de553acf11f7b 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 @@ -16,7 +16,7 @@ import { KibanaLogic } from '../../../shared/kibana'; import './product_card.scss'; -interface IProductCard { +interface ProductCardProps { // Expects product plugin constants (@see common/constants.ts) product: { ID: string; @@ -27,7 +27,7 @@ interface IProductCard { image: string; } -export const ProductCard: React.FC = ({ product, image }) => { +export const ProductCard: React.FC = ({ product, image }) => { const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic); const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx index 235ececd8b6fc..579baf41f2ce3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -30,14 +30,14 @@ import { SetupGuideCta } from '../setup_guide'; import AppSearchImage from '../../assets/app_search.png'; import WorkplaceSearchImage from '../../assets/workplace_search.png'; -interface IProductSelectorProps { +interface ProductSelectorProps { access: { hasAppSearchAccess?: boolean; hasWorkplaceSearchAccess?: boolean; }; } -export const ProductSelector: React.FC = ({ access }) => { +export const ProductSelector: React.FC = ({ access }) => { const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 048baabe6a1dd..b562cd577c56b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -9,7 +9,7 @@ import { Route, Switch } from 'react-router-dom'; import { useValues } from 'kea'; import { KibanaLogic } from '../shared/kibana'; -import { IInitialAppData } from '../../../common/types'; +import { InitialAppData } from '../../../common/types'; import { HttpLogic } from '../shared/http'; @@ -21,7 +21,7 @@ import { SetupGuide } from './components/setup_guide'; import './index.scss'; -export const EnterpriseSearch: React.FC = ({ access = {} }) => { +export const EnterpriseSearch: React.FC = ({ access = {} }) => { const { errorConnecting } = useValues(HttpLogic); const { config } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index b9c94e351089d..3436df851c8d8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -14,7 +14,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { AppMountParameters, CoreStart } from 'src/core/public'; import { PluginsStart, ClientConfigType, ClientData } from '../plugin'; -import { IInitialAppData } from '../../common/types'; +import { InitialAppData } from '../../common/types'; import { mountKibanaLogic } from './shared/kibana'; import { mountLicensingLogic } from './shared/licensing'; @@ -29,7 +29,7 @@ import { externalUrl } from './shared/enterprise_search_url'; */ export const renderApp = ( - App: React.FC, + App: React.FC, { params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart }, { config, data }: { config: ClientConfigType; data: ClientData } ) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts index 5a05a03adeb6b..7271a1dbbea39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts @@ -15,12 +15,12 @@ export interface IFlashMessage { description?: ReactNode; } -export interface IFlashMessagesValues { +interface FlashMessagesValues { messages: IFlashMessage[]; queuedMessages: IFlashMessage[]; historyListener: Function | null; } -export interface IFlashMessagesActions { +interface FlashMessagesActions { setFlashMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearFlashMessages(): void; setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; @@ -31,7 +31,7 @@ export interface IFlashMessagesActions { const convertToArray = (messages: IFlashMessage | IFlashMessage[]) => !Array.isArray(messages) ? [messages] : messages; -export const FlashMessagesLogic = kea>({ +export const FlashMessagesLogic = kea>({ path: ['enterprise_search', 'flash_messages_logic'], actions: { setFlashMessages: (messages) => ({ messages: convertToArray(messages) }), diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts index 2bd04d1d87f7d..c4b287ee08354 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -17,7 +17,7 @@ import { FlashMessagesLogic, IFlashMessage } from './'; * `errors` property in the response's data, which will contain messages we can * display to the user. */ -interface IErrorResponse { +interface ErrorResponse { statusCode: number; error: string; message: string; @@ -25,17 +25,14 @@ interface IErrorResponse { errors: string[]; }; } -interface IOptions { +interface Options { isQueued?: boolean; } /** * Converts API/HTTP errors into user-facing Flash Messages */ -export const flashAPIErrors = ( - error: HttpResponse, - { isQueued }: IOptions = {} -) => { +export const flashAPIErrors = (error: HttpResponse, { isQueued }: Options = {}) => { const defaultErrorMessage = 'An unexpected error occurred'; const errorFlashMessages: IFlashMessage[] = Array.isArray(error?.body?.attributes?.errors) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index 21c1a60efa6b7..8792c26f9bad4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -5,12 +5,6 @@ */ export { FlashMessages } from './flash_messages'; -export { - FlashMessagesLogic, - IFlashMessage, - IFlashMessagesValues, - IFlashMessagesActions, - mountFlashMessagesLogic, -} from './flash_messages_logic'; +export { FlashMessagesLogic, IFlashMessage, mountFlashMessagesLogic } from './flash_messages_logic'; export { flashAPIErrors } from './handle_api_errors'; export { setSuccessMessage, setErrorMessage, setQueuedSuccessMessage } from './set_message_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx index 9b0833dfce541..69176b8a139e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx @@ -7,18 +7,18 @@ import React, { useState, ReactElement } from 'react'; import { i18n } from '@kbn/i18n'; -interface IChildrenProps { +interface ChildrenProps { toggle: () => void; isHidden: boolean; hiddenText: React.ReactNode; } -interface IProps { +interface Props { text: string; - children(props: IChildrenProps): ReactElement; + children(props: ChildrenProps): ReactElement; } -export const HiddenText: React.FC = ({ text, children }) => { +export const HiddenText: React.FC = ({ text, children }) => { const [isHidden, toggleIsHidden] = useState(true); const hiddenLabel = i18n.translate('xpack.enterpriseSearch.hiddenText', { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index d16e507bfb3bc..76cefa4bc5a2c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -10,20 +10,20 @@ import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/ import { READ_ONLY_MODE_HEADER } from '../../../../common/constants'; -export interface IHttpValues { +interface HttpValues { http: HttpSetup; httpInterceptors: Function[]; errorConnecting: boolean; readOnlyMode: boolean; } -export interface IHttpActions { +interface HttpActions { initializeHttpInterceptors(): void; setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean }; setReadOnlyMode(readOnlyMode: boolean): { readOnlyMode: boolean }; } -export const HttpLogic = kea>({ +export const HttpLogic = kea>({ path: ['enterprise_search', 'http_logic'], actions: { initializeHttpInterceptors: () => null, @@ -108,12 +108,12 @@ export const HttpLogic = kea>({ /** * Mount/props helper */ -interface IHttpLogicProps { +interface HttpLogicProps { http: HttpSetup; errorConnecting?: boolean; readOnlyMode?: boolean; } -export const mountHttpLogic = (props: IHttpLogicProps) => { +export const mountHttpLogic = (props: HttpLogicProps) => { HttpLogic(props); const unmount = HttpLogic.mount(); return unmount; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts index 46a52415f8564..5e41ea032d503 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HttpLogic, IHttpValues, IHttpActions, mountHttpLogic } from './http_logic'; +export { HttpLogic, mountHttpLogic } from './http_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 89ed07f302b03..28f500a2c8a39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -11,9 +11,9 @@ import { History } from 'history'; import { ApplicationStart, ChromeBreadcrumb } from 'src/core/public'; import { HttpLogic } from '../http'; -import { createHref, ICreateHrefOptions } from '../react_router_helpers'; +import { createHref, CreateHrefOptions } from '../react_router_helpers'; -interface IKibanaLogicProps { +interface KibanaLogicProps { config: { host?: string }; history: History; navigateToUrl: ApplicationStart['navigateToUrl']; @@ -21,17 +21,17 @@ interface IKibanaLogicProps { setDocTitle(title: string): void; renderHeaderActions(HeaderActions: FC): void; } -export interface IKibanaValues extends IKibanaLogicProps { - navigateToUrl(path: string, options?: ICreateHrefOptions): Promise; +export interface KibanaValues extends KibanaLogicProps { + navigateToUrl(path: string, options?: CreateHrefOptions): Promise; } -export const KibanaLogic = kea>({ +export const KibanaLogic = kea>({ path: ['enterprise_search', 'kibana_logic'], reducers: ({ props }) => ({ config: [props.config || {}, {}], history: [props.history, {}], navigateToUrl: [ - (url: string, options?: ICreateHrefOptions) => { + (url: string, options?: CreateHrefOptions) => { const deps = { history: props.history, http: HttpLogic.values.http }; const href = createHref(url, deps, options); return props.navigateToUrl(href); @@ -44,7 +44,7 @@ export const KibanaLogic = kea>({ }), }); -export const mountKibanaLogic = (props: IKibanaLogicProps) => { +export const mountKibanaLogic = (props: KibanaLogicProps) => { KibanaLogic(props); const unmount = KibanaLogic.mount(); return unmount; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index e22334aeea371..25d4850425a6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -23,15 +23,15 @@ import { letBrowserHandleEvent, createHref } from '../react_router_helpers'; * Types */ -interface IBreadcrumb { +interface Breadcrumb { text: string; path?: string; // Used to navigate outside of the React Router basename, // i.e. if we need to go from App Search to Enterprise Search shouldNotCreateHref?: boolean; } -export type TBreadcrumbs = IBreadcrumb[]; -export type TBreadcrumbTrail = string[]; // A trail of breadcrumb text +export type Breadcrumbs = Breadcrumb[]; +export type BreadcrumbTrail = string[]; // A trail of breadcrumb text /** * Generate an array of breadcrumbs based on: @@ -50,7 +50,7 @@ export type TBreadcrumbTrail = string[]; // A trail of breadcrumb text * > Source Prioritization (linked to `/groups/{example-group-id}/source_prioritization`) */ -export const useGenerateBreadcrumbs = (trail: TBreadcrumbTrail): TBreadcrumbs => { +export const useGenerateBreadcrumbs = (trail: BreadcrumbTrail): Breadcrumbs => { const { history } = useValues(KibanaLogic); const pathArray = stripLeadingSlash(history.location.pathname).split('/'); @@ -65,7 +65,7 @@ export const useGenerateBreadcrumbs = (trail: TBreadcrumbTrail): TBreadcrumbs => * https://elastic.github.io/eui/#/navigation/breadcrumbs */ -export const useEuiBreadcrumbs = (breadcrumbs: TBreadcrumbs): EuiBreadcrumb[] => { +export const useEuiBreadcrumbs = (breadcrumbs: Breadcrumbs): EuiBreadcrumb[] => { const { navigateToUrl, history } = useValues(KibanaLogic); const { http } = useValues(HttpLogic); @@ -89,7 +89,7 @@ export const useEuiBreadcrumbs = (breadcrumbs: TBreadcrumbs): EuiBreadcrumb[] => * Product-specific breadcrumb helpers */ -export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEuiBreadcrumbs([ { text: ENTERPRISE_SEARCH_PLUGIN.NAME, @@ -99,10 +99,10 @@ export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) = ...breadcrumbs, ]); -export const useAppSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useAppSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEnterpriseSearchBreadcrumbs([{ text: APP_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]); -export const useWorkplaceSearchBreadcrumbs = (breadcrumbs: TBreadcrumbs = []) => +export const useWorkplaceSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEnterpriseSearchBreadcrumbs([ { text: WORKPLACE_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index de5f72de79192..a0e34106fe2a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -15,24 +15,24 @@ import { * https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.chromedoctitle.md */ -export type TTitle = string[]; +type Title = string[]; /** * Given an array of page titles, return a final formatted document title * @param pages - e.g., ['Curations', 'some Engine', 'App Search'] * @returns - e.g., 'Curations - some Engine - App Search' */ -export const generateTitle = (pages: TTitle) => pages.join(' - '); +export const generateTitle = (pages: Title) => pages.join(' - '); /** * Product-specific helpers */ -export const enterpriseSearchTitle = (page: TTitle = []) => +export const enterpriseSearchTitle = (page: Title = []) => generateTitle([...page, ENTERPRISE_SEARCH_PLUGIN.NAME]); -export const appSearchTitle = (page: TTitle = []) => +export const appSearchTitle = (page: Title = []) => generateTitle([...page, APP_SEARCH_PLUGIN.NAME]); -export const workplaceSearchTitle = (page: TTitle = []) => +export const workplaceSearchTitle = (page: Title = []) => generateTitle([...page, WORKPLACE_SEARCH_PLUGIN.NAME]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index a43e7053bb1e1..0e694a3d2bbc8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -14,7 +14,7 @@ import { useEnterpriseSearchBreadcrumbs, useAppSearchBreadcrumbs, useWorkplaceSearchBreadcrumbs, - TBreadcrumbTrail, + BreadcrumbTrail, } from './generate_breadcrumbs'; import { enterpriseSearchTitle, appSearchTitle, workplaceSearchTitle } from './generate_title'; @@ -33,11 +33,11 @@ import { enterpriseSearchTitle, appSearchTitle, workplaceSearchTitle } from './g * Title output: Workplace Search - Elastic */ -interface ISetChromeProps { - trail?: TBreadcrumbTrail; +interface SetChromeProps { + trail?: BreadcrumbTrail; } -export const SetEnterpriseSearchChrome: React.FC = ({ trail = [] }) => { +export const SetEnterpriseSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); @@ -54,7 +54,7 @@ export const SetEnterpriseSearchChrome: React.FC = ({ trail = [ return null; }; -export const SetAppSearchChrome: React.FC = ({ trail = [] }) => { +export const SetAppSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); @@ -71,7 +71,7 @@ export const SetAppSearchChrome: React.FC = ({ trail = [] }) => return null; }; -export const SetWorkplaceSearchChrome: React.FC = ({ trail = [] }) => { +export const SetWorkplaceSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); const title = reverseArray(trail); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx index ef8216e8b6711..0ee7de242dfe2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import './layout.scss'; -interface ILayoutProps { +interface LayoutProps { navigation: React.ReactNode; restrictWidth?: boolean; readOnlyMode?: boolean; @@ -23,7 +23,7 @@ export interface INavContext { } export const NavContext = React.createContext({}); -export const Layout: React.FC = ({ +export const Layout: React.FC = ({ children, navigation, restrictWidth, 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 facfd0bfcb16d..6c4e1d084c16d 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 @@ -23,7 +23,7 @@ import './side_nav.scss'; * Side navigation - product & icon + links wrapper */ -interface ISideNavProps { +interface SideNavProps { // Expects product plugin constants (@see common/constants.ts) product: { NAME: string; @@ -31,7 +31,7 @@ interface ISideNavProps { }; } -export const SideNav: React.FC = ({ product, children }) => { +export const SideNav: React.FC = ({ product, children }) => { return (